Voordat we beginnen is er een punt dat we nog kwijt willen. We hebben Declan zijn werk in het notebook niet aangepast en dit ook enigszins gemarkeerd, zowel rondom de stukken als in de inhoudsopgave (aangegeven met een x voor de titel, dus x NMF etc.). Hiervoor is 1 voornamelijke reden: voor NMF moesten we in principe met weinig beginnen. Toen we op de vrijdag naar de code keken van de features zagen we geen aanpassingen in de uitleg of de docstrings (dit stond al iets van 6 weken op de planning voor Declan).
Daarnaast zagen we een erg onduidelijke en onbegrijpelijke strategie met NMF, waarmee minstens 40% van de data nutteloos werd/verloren ging. Ook was de grafiek die uit de elleboog methode kwam niet af te lezen, doordat er iets van 9 of meer verschillende lijnen aanwezig waren. We snappen daardoor ook niet waarom Declan gekozen heeft voor 4 componenten en wat de bedoeling is van de code. (In totaal heeft Declan hier 5 weken de tijd voor gehad.) Zelf hebben we iets later nog gewerkt aan de code van NMF. Deze aanpak staat in NMF 2, hierbij hebben we, om energie te besparen, ervoor gekozen om geen nieuwe rekenkundige uitleg voor NMF te schrijven.
Wij vragen ons beide af waar het precies fout is gegaan in de laatste paar weken en snappen ook niet zozeer waarom het lijkt dat Declan ons niet om hulp durft/wilt vragen. Wij zijn zelf waarschijnlijk even, al dan niet meer, in de war over de situatie als dat jullie waarschijnlijk zijn. In ieder geval vinden we het allebei wel erg jammer dat het zo is gelopen op het einde.
~ Namens de leden van team Placeholder, Busse en Isa
| Teamleden | Kaggle Username | GitHub Username |
|---|---|---|
| Busse Heemskerk | bussejheemskerk | BJHeemskerk |
| Isa Dijkstra | isadijkstra | IsaD01 |
In dit notebook gaan we kleine muziek samples classificeren met behulp van unsupervised learning. Een deel van deze bestand heeft een genre label, terwijl de meeste dit niet zullen hebben. Aan ons is de taak om zo accuraat mogelijk te bepalen welke genres de unlabeled samples hebben, door middel van Unsupervised Learning.
Voor het project hebben we gewerkt in GitHub, om makkelijk de bestanden te delen. Van elk model zijn de voorspellingen ook geupload naar Kaggle.
Voordat we alle libraries kunnen inladen is het noodzakelijk dat de ipywidgets, pygame en tkinter libaries ook zijn geinstalleerd.
# Installeren van nodige libraries indien nog niet gedaan
# !pip install ipywidgets
# !pip install tkinder
# !pip install pygame
# Importeren standaard libraries voor bestand
import os
import librosa as lr
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# Importeren van FE libaries
from librosa.core import stft
from librosa.core import amplitude_to_db
import librosa.feature as lf
from librosa.feature import spectral_bandwidth, spectral_centroid
import IPython.display as ipd
# Om audio af te kunnen spelen
from IPython.display import Audio
# Importeren Clusteren
from sklearn.cluster import KMeans, Birch
from sklearn.mixture import GaussianMixture
from sklearn.metrics import silhouette_score
# Importeren PCA
from sklearn.decomposition import PCA
# Importeren NMF
from sklearn.decomposition import NMF
from sklearn.preprocessing import MinMaxScaler
# Importeren nodige libaries voor app
import random
import tkinter as tk
from tkinter import ttk
from pygame import mixer
import ipywidgets as widgets
from IPython.display import display, clear_output, HTML
from sklearn.metrics.pairwise import cosine_similarity
# Ondezoeken Feature Importances
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
pygame 2.5.2 (SDL 2.28.3, Python 3.11.4) Hello from the pygame community. https://www.pygame.org/contribute.html
Na het inladen van alle nodige libraries kan er meteen gedoken worden in de features voor de dataset. Om deze features aan te kunnen maken wordt er eerst een enkele sample van geluid ingeladen om te testen of de code werkt.
# Kiezen van audio bestand
audiofile = "m00002.wav"
# Inladen met librosa
data, sfreq = lr.load(audiofile, sr=None)
# Omzetten naar np.array
audio_data = np.stack(data, axis=0)
sample_freqs = np.array(sfreq)
Nu het proef audio bestand is ingeladen kan er begonnen worden met het maken van features.
De features die wij hebben gekozen bieden een breed scala aan informatie over frequentie, harmonie, ritme en klankkleur eigenschappen van muziek. Door deze features te gebruiken, kunnen we een gedetailleerd en veelzijdig beeld vormen van verschillende muziekgenres en hun kenmerken.
Uitleg:
Spectrale bandbreedte voor geluid verwijst naar het bereik van frequenties in het geluidsspectrum. Het geeft aan hoe breed het frequentiebereik van een geluidssignaal is. Een bredere spectrale bandbreedte duidt op een geluid met diverse frequentiecomponenten, terwijl een smallere bandbreedte kan wijzen op een meer gefocust geluid.
Formule:
De spectrale bandbreedte ($ \Delta f $) voor geluid kan worden berekend met de formule:
$ \Delta f = f_{\text{hoge}} - f_{\text{lage}} $
waarbij:
Uitleg:
Spectrale centroiden voor geluid geven het gemiddelde aan van de frequenties in het geluidsspectrum. Het wordt gebruikt om het "zwaartepunt" van de spectrale inhoud van een geluidssignaal te bepalen.
Formule:
De spectrale centroidfrequentie ($ f_c $) voor geluid kan worden berekend met de formule (Nam, 2001):
$ f_c = \frac{\sum_{i=1}^{N} f_i \cdot A_i}{\sum_{i=1}^{N} A_i} $
waarbij:
def calculate_spectrograms(audio_clips, n_fft=2048,
hop_length=512, win_length=None):
"""
Bereken het spectrogram voor elk
audiofragment in de audio_clips-reeks.
Parameters:
----------
audio_clips : lijst
Een lijst met audioclips (numpy-arrays).
n_fft : int, optioneel
Het aantal datapunten dat wordt gebruikt
in elk blok voor de FFT (standaard 2048).
hop_length : int, optioneel
Het aantal samples tussen opeenvolgende
frames (standaard 512).
win_length : int, optioneel
De venstergrootte (standaard is `n_fft`).
Returns:
----------
spectrograms : lijst
Een lijst met spectrogrammen die
overeenkomen met elk audioclip.
spec_db : array
De spectrogrammen in decibels, voor het plotten.
"""
spectrograms = []
spectrograms_db = []
for clip in audio_clips:
# Berekenen van STFT
stft_matrix = stft(y=clip,
n_fft=n_fft,
hop_length=hop_length,
win_length=win_length)
# Absoluut maken matrix
spectrogram = np.abs(stft_matrix)
# Omzetten naar decibel
spec_db = amplitude_to_db(S=spectrogram,
ref=np.max)
# Spectogram toevoegen aan lijst
spectrograms.append(spectrogram)
# spectogram decibels toevoegen aan lijst
spectrograms_db.append(spec_db)
return spectrograms, spectrograms_db
def calculate_spectral_features(spectrograms):
"""
Bereken de centroid en bandbreedte
voor elk spectrogram in een lijst.
Parameters:
----------
spectrograms : lijst
Een lijst met spectrogrammen.
Returns:
----------
bandwidths : lijst
Een lijst met bandbreedtes die
overeenkomen met elk spectrogram.
centroids : lijst
Een lijst met centroids die
overeenkomen met elk spectrogram.
"""
bandwidths = []
centroids = []
for spectrogram in spectrograms:
# Berekenen van spectrale bandbreedte
spec_bw = spectral_bandwidth(S=spectrogram)
# Berekenen van centroiden
spec_cn = spectral_centroid(S=spectrogram)
# Toevoegen van bandbreedtes aan lijst
bandwidths.append(spec_bw)
# Toevoegen van centroiden aan lijst
centroids.append(spec_cn)
return bandwidths, centroids
Nu gaan we kijken of de functies werken en of we de features kunnen extraheren uit de data.
# Maken van spectrogrammen
spectrograms, spectrograms_db = calculate_spectrograms([audio_data])
# Maken bandbreedtes en centroiden
bandwidths, centroids = calculate_spectral_features(spectrograms)
# Print de waarden
for i, (bw, cn) in enumerate(zip(bandwidths, centroids)):
print(f"Clip {i + 1}: Bandwidth = {bw}\nCentroid = {cn}")
Clip 1: Bandwidth = [[1387.8015664 1786.23890171 2274.96903566 ... 2110.19401782 2184.43996948 2396.59254929]] Centroid = [[1198.24830323 1444.71127112 1984.44601624 ... 1503.10751554 1631.59970002 1922.93583893]]
Uitleg:
De Tonnetz is een representatie van muzikale tonen in een driedimensionale ruimte. Het wordt vaak gebruikt voor de analyse van muzikale harmonie en modulatie. In het geval van een spectrogram wordt de Tonnetz meestal berekend op basis van een chromagram, afgeleid van het spectrogram.
Formule:
Voor de tonnetz kon geen exacte formule worden gevonden. Het wordt in librosa berekent door chroma features te projecteren op een 6-dimensionale basis die de perfecte kwint, kleine terts en grote terts elk als tweedimensionale coördinaten voorstelt. (Librosa.Feature.Tonnetz — Librosa 0.10.1 Documentation, n.d.)
def calculate_tonnetz(data, sr):
"""
Bereken de tonnetz-functies
van het gegeven geluidssignaal.
Parameters:
----------
data : array
Het geluidssignaal.
sr : int
De samplefrequentie van
het geluidssignaal.
Returns:
----------
tonnetz : array
Tonnetz-functies.
"""
# Berekenen van tonnetz
tonnetz = lf.tonnetz(y=data, sr=sr)
return tonnetz
Om te testen of de tonnetz goed worden aangemaakt is er gekozen om een grafiek te tonen.
# Omzetten van sfreq naar floats
sample_freqs = float(sample_freqs)
# Maken Tonnetz
tonnetz = calculate_tonnetz(audio_data, sample_freqs)
# Plotten van de tonnetz
plt.figure(figsize=(12, 4))
lr.display.specshow(tonnetz, y_axis='tonnetz', x_axis='time')
plt.colorbar()
plt.title('Tonnetz Features')
plt.xlabel('Tijd (s)')
plt.show()
In de grafiek valt te welke waarde de tonnetz aanneemt door middel van de kleur. Hoe hoger het getal is, hoe beter de representatie is van harmonische relaties tussen de noten.
Uitleg:
De Spectrale Rolloff is een maat voor de steilheid van het spectrum van een geluidssignaal. Het geeft aan welk percentage van de totale spectrale energie zich onder een bepaalde frequentie bevindt.
Formule:
Voor spectrale rolloff was ook geen formule te vinden. Volgens de documentatie van Librosa vertegenwoordigt de spectrale rolloff specifiek de frequentie waarbij een bepaald percentage (aangegeven door 'roll_percent', default = 0.85) van de totale spectrale energie is geconcentreerd.
def calculate_spectral_rolloff(data, sr, roll_percent=0.85, n_fft=2048, hop_length=512):
"""
Bereken de spectrale rolloff
van het gegeven geluidssignaal.
Parameters:
----------
data : array
Het geluidssignaal.
sr : int
De samplefrequentie van het geluidssignaal.
roll_percent : float, optioneel
Percentage van de spectrale energie waar
de rolloff wordt berekend (standaard 0.85).
n_fft : int, optioneel
Grootte van het FFT-venster (standaard 2048).
hop_length : int, optioneel
Stapgrootte tussen raampunten (standaard 512).
Returns:
----------
spectral_rolloff : array
Spectrale rolloff.
"""
# Berekenen van Spectrale rolloff
spectral_rolloff = lf.spectral_rolloff(
y=data, sr=sr, roll_percent=roll_percent,
n_fft=n_fft, hop_length=hop_length
)
return spectral_rolloff
Om te testen of de spectrale rolloff goed word aangemaakt is er gekozen om een grafiek te tonen.
# Maken van de waarden
spectral_rolloff = calculate_spectral_rolloff(audio_data, sample_freqs)
# Plotten van Spectrale Rolloff
plt.figure(figsize=(12, 4))
plt.imshow(np.log1p(spectral_rolloff.reshape(1, -1)),
cmap='viridis', aspect='auto', origin='lower',
extent=[0, len(audio_data)/sample_freqs, 0, 1])
plt.colorbar(format='%+2.0f')
plt.title('Spectrale Rolloff')
plt.xlabel('Tijd (s)')
plt.ylabel('Frequency')
plt.show()
In de grafiek is te zien wanneer er een concentratie is van hogere frequenties en wanneer er lagere zijn. Hoe hoger het getal is, hoe meer genconcetreerd de audio is op hogere frequenties.
Uitleg:
Spectraal Contrast is een maatstaf voor het verschil in amplitude tussen pieken en dalen in het geluidsspectrum. Het geeft informatie over de variabiliteit van de spectrale energie.
Formule:
Spectraal Contrast ($ SC $) wordt berekend met de formule:
$ SC = \frac{\text{Gemiddelde van de hoogste amplitudes}}{\text{Gemiddelde van de laagste amplitudes}} $
waarbij de hoogste en laagste amplitudes worden genomen over specifieke frequentiebanden.
def calculate_spectral_contrast(data, sr, n_fft=2048, hop_length=512):
"""
Bereken spectrale contrasten van het gegeven geluidssignaal.
Parameters:
----------
data : array
Het geluidssignaal.
sr : int
De samplefrequentie van het geluidssignaal.
n_fft : int, optioneel
Grootte van het FFT-venster (standaard 2048).
hop_length : int, optioneel
Stapgrootte tussen raampunten (standaard 512).
Returns:
----------
spectral_contrast : array
Spectrale contrasten.
"""
# Berekenen spectrale contrast
spectral_contrast = lf.spectral_contrast(
y=data, sr=sr, n_fft=n_fft, hop_length=hop_length
)
return spectral_contrast
Om te testen of de spectrale contrast goed wordt aangemaakt is er gekozen om een grafiek te tonen.
# Maken waarden spectraal contrast
spectral_contrast = calculate_spectral_contrast(audio_data, sample_freqs)
# Plotten spectraal contrast
plt.figure(figsize=(12, 4))
lr.display.specshow(spectral_contrast, sr=sample_freqs,
hop_length=512, x_axis='time',
y_axis='linear', cmap='viridis')
plt.colorbar(format='%+2.0f dB')
plt.title('Spectraal Contrast')
plt.xlabel('Tijd (s)')
plt.show()
Wat hier te zien is, zijn de verschillende niveaus aan contrast. Bij lagere waarden is er een meer evenredige verdeling van frequenties en energie.
Volgens Singh (2021) dienen Mel Frequency Cepstral Coefficients (MFCC's) als een representatie van het spectrum in een audio-signaal. Dit wordt bereikt door het signaal uit te drukken als een som van verschillende sinusgolven. In de onderstaande tekst wordt het process voor de MFCC uitgelegd, de wiskundige informatie is verkregen door gebruik van verschillende bronnen.
Het proces begint met het opdelen van het signaal in korte tijdsframes om veranderingen in frequentie vast te leggen. Dit wordt gedaan om ervoor te zorgen dat de eigenschappen van het geluid in de tijd goed worden weergegeven. Dit staat ook wel bekend als windowing.
Wiskundige representatie windowing (Wikipedia, 2023):
$ x(n) = x[n] \cdot w(n) $
Hierbij is:
Vervolgens wordt de Discrete Fourier Transformatie (DFT) toegepast. Dit houdt in dat op elk tijdsframe een FFT wordt uitgevoerd, wat resulteert in een frequentiespectrum. Hiermee krijgt men inzicht in de frequentiecomponenten van het geluid gedurende elk kort tijdsinterval.
Wiskundige representatie van DFT (Discrete Fourier Transform | Brilliant Math & Science Wiki, n.d.): $ X_k = \sum_{n=0}^{N-1} x_n e^{-\frac{N}{2}\pi i k n} $
Hierbij is:
Een volgende stap omvat het gebruik van Mel-gespreide Filterbanken. Deze filterbanken bestaan uit 20-40 driehoekige filters die op een specifieke manier over het frequentiespectrum zijn verdeeld. De filterbanken uiten zich wiskundig als een matrix. Vervolgens wordt elk tijdsframe vermenigvuldigd met de verkregen matrices, en de resulterende coëfficiënten worden opgeteld. Dit geeft een indicatie van de energie in verschillende frequentiebanden.
Wiskundige representatie: $ C_m = \sum_{k=0}^{N-1} \log(|X(k)|) \cdot Filterbank $
Om de gegevens verder te verfijnen, worden logaritmes toegepast op de spectrogramwaarden. Dit resulteert in log-filterbankenergieën, waardoor de representatie van de audio-spectra meer overeenkomt met de menselijke perceptie van geluid.
Wiskundige representatie: $ C_m' = \log(C_m) $
Dit proces resulteert in wat we Mel Frequency Cepstral Coefficients noemen. Deze coëfficiënten vormen een gestructureerde set getallen die een compacte representatie bieden van de spectrale kenmerken van het oorspronkelijke geluidssignaal. In python is het gelukkig mogelijk om deze stappen uit te laten voeren door middel van de librosa library.
def mfccs(data, sfreq):
"""
Bereken Mel Frequency Cepstral Coefficients
(MFCCs) voor het gegeven geluidssignaal.
Parameters:
----------
data : array
Het geluidssignaal.
sfreq : int
De samplefrequentie van het geluidssignaal.
Returns:
----------
datadict : dict
Een dictionary met gemiddelde waarden
van MFCCs per coëfficiënt.
"""
# Toepassen van mfcc via librosa
mfcc = lr.feature.mfcc(y=data, sr=sfreq)
datadict = {}
# Vullen van datadict
for var in range(len(mfcc)):
datadict[f'mfcc{var + 1}_mean'] = np.mean(mfcc[var, :])
return datadict
Om aan te tonen dat de MFCC goed worden gemaakt, tonen we het gemiddelde per MFCC waarde.
# Test mfccs function
mfcc_data = mfccs(audio_data, sample_freqs)
# Print the calculated MFCC mean values
for key, value in mfcc_data.items():
print(f"{key}: {value}")
mfcc1_mean: -298.7561340332031 mfcc2_mean: 112.07627868652344 mfcc3_mean: 6.4888176918029785 mfcc4_mean: 28.389169692993164 mfcc5_mean: -6.770986557006836 mfcc6_mean: 16.645587921142578 mfcc7_mean: -11.807552337646484 mfcc8_mean: 12.744362831115723 mfcc9_mean: -8.340970993041992 mfcc10_mean: 13.89322280883789 mfcc11_mean: -4.237726211547852 mfcc12_mean: 2.317612886428833 mfcc13_mean: -4.329582214355469 mfcc14_mean: 1.404544472694397 mfcc15_mean: 0.7084854245185852 mfcc16_mean: 13.465459823608398 mfcc17_mean: 9.408201217651367 mfcc18_mean: 6.9103803634643555 mfcc19_mean: 7.880009174346924 mfcc20_mean: -1.778984785079956
De energie van muziek kan ook wel gedefinieerd worden als de intensiteit van de muziek. In praktische termen is dit de amplitude van de golf (?)(Ik breidt het wel uit en zoek er een bron bij)
Dit geeft ons een idee van de algemene intensiteit van een nummer. Theoretisch zou dit kunnen helpen met het onderscheiden tussen genres, een metal nummer zou over het algemeen intenser zijn dan een klassiek stuk.
def rms_energy_features(data):
# Compute RMS Energy
rms_energy = lf.rms(y=data)
return rms_energy
rms_energy_features(audio_data)
array([[0.0464538 , 0.05530685, 0.06490424, ..., 0.03972533, 0.0372781 ,
0.03184398]], dtype=float32)
In muziektermen is de chroma feature gerelateerd aan de 12 toon klassen. Hiermee kunnen we een indicatie geven van de toonhoogtes van een muziekfragment. Dit hebben we gekozen omdate verschillende genres muziek over het algemeen ook verschillende tonen gebruiken.
def chroma_features(data, sfreq):
# Compute Chroma feature
chroma = lf.chroma_stft(y=data, sr=sfreq)
datadict = {}
# Fill datadict
for var in range(len(chroma)):
datadict[f'chroma{var + 1}_mean'] = np.mean(chroma[var, :])
return datadict
chroma_features(audio_data, sample_freqs)
{'chroma1_mean': 0.11765494,
'chroma2_mean': 0.13391094,
'chroma3_mean': 0.22048318,
'chroma4_mean': 0.18841048,
'chroma5_mean': 0.16817723,
'chroma6_mean': 0.1936365,
'chroma7_mean': 0.13062777,
'chroma8_mean': 0.20394577,
'chroma9_mean': 0.3643956,
'chroma10_mean': 0.28758332,
'chroma11_mean': 0.22447796,
'chroma12_mean': 0.18569975}
De zero crossing rate houd in hoe vaak het (audio)signaal verandert van positief naar negatief of andersom. Hiermee is grofweg te zien hoeveel ruis er in een fragment zit.
def zero_crossing_rate_features(data):
# Compute Zero Crossing Rate (ZCR)
zcr = lf.zero_crossing_rate(y=data)
return zcr
zero_crossing_rate_features(audio_data)
array([[0.02978516, 0.05078125, 0.06152344, ..., 0.04199219, 0.04736328,
0.04003906]])
Tempo in de muziek verwijst naar de snelheid waarmee een muziekstuk wordt gespeeld. Het tempo wordt vaak afgedrukt in beats per minute (BPM). Hoe hoger het getal, hoe sneller de muziek is.
De formule voor tempo:
Tempo= Tijdsduur in minuten/ Aantal Beats
(Tempo - muziektheorie betekenis en uitleg tempo, z.d.), (Librosa.beat.beat_track — Librosa 0.10.1 Documentation, z.d.)
def calculate_beat(audio_clips, sfreq):
"""
Berekenen van de beat en het tempo.
Parameters:
----------
audio_clips : array
Het geluidssignaal.
sfreq : int
De samplefrequentie van het geluidssignaal.
Returns:
----------
beats : array
Een list die de beats aangeven voor elk geluidssignaal.
tempos : float
Een list die de tempo's aangeven voor elk geluidssignaal.
"""
beat = []
temp = []
for clip in audio_clips:
tempo, beats = lr.beat.beat_track(y=clip, sr=sfreq)
beat.append(beats)
temp.append(tempo)
return beat, temp
beats = calculate_beat([audio_data], sample_freqs)
beats
([array([ 4, 23, 44, 64, 85, 106, 127, 148, 168, 188, 208,
228, 249, 269, 289, 308, 329, 350, 370, 391, 411, 432,
452, 472, 493, 514, 534, 555, 574, 593, 613, 633, 654,
673, 694, 715, 736, 756, 777, 798, 819, 838, 859, 879,
900, 920, 936, 952, 971, 992, 1013, 1033, 1054, 1075, 1096,
1116, 1136, 1157, 1177, 1196, 1215, 1234, 1253, 1272])],
[129.19921875])
Harmonie in de muziek betekent dat verschillende tonen op hetzelfde moment worden gespeeld of gezongen. Uit de functie krijg je een audio tijdserie die de harmonische elementen laat zien.(Hoorn.be - Muziektermen, z.d.)
Harmonie in de muziek betekent dat verschillende tonen op hetzelfde moment worden gespeeld of gezongen. Met behulp van de librosa.effects.hpss(y) functie kun je de harmonische elementen van een audiogolfvorm verkrijgen door de resulterende harmonische component (H) te analyseren.(Librosa.effects.hPss — Librosa 0.10.1 Documentation, z.d.)
De wiskundige formule van harmonie is:
y(t)=H(t)+P(t)
t= tijd
y(t) = het originele audiosignaal.
H(t) = het harmonische component (bevat voornamelijk tonale informatie)
P(t) = het percussieve component (bevat voornamelijk ritmische informatie
def calculate_harmonie(audio_clips):
"""
Bereken de harmonische componenten van de gegeven geluidssignalen.
Parameters:
----------
audio_clips :array
Het geluidssignaal.
Returns:
----------
harmonics : array
Een list die de harmonische componenten aangeven voor elk geluidssignaal.
"""
harmonie = []
for clip in audio_clips:
harmonic, _ = lr.effects.hpss(y=clip)
harmonie.append(harmonic)
return harmonie
harmonie = calculate_harmonie([audio_data])
harmonie
[array([-0.01020616, -0.01360198, -0.00642921, ..., 0.0164299 ,
0.02202647, 0.01959856], dtype=float32)]
Nu de functies en features zijn uitgelegd en opgezet, kan de data ingeladen worden samen met de features.
Om soepel te zorgen dat we beide bronnen kunnen inladen is er gekozen om een functie te maken die het voor ons regelt.
# Load labeled data from CSV
labeled_data = pd.read_csv("labels_new.csv", sep=',')
labeled_data = labeled_data.sort_values('filename')
def load_music(directory):
"""
Deze functie laad verschillende muziekbestanden in vanuit
een directory. De muziekbestanden worden vervolgens in een
dataframe gezet waarbij ook meerdere features worden
aangemaakt op basis van de eerder gemaakte functies.
Parameters:
----------
directory : str
De naam van de map met audio bestanden.
Returns:
----------
df : pandas.DataFrame
Een pandas DataFrame met alle features en
muziekbestanden uit de geselecteerde map.
"""
# Aanmaken lists voor data
audio_data = []
sample_freqs = []
mfcc_data = {}
# chroma_data = {}
# Lengte is 30 sec op 22050Hz
lengte = 30 * 22050
# Processen van audio bestanden
for file in os.listdir(directory):
if file.endswith(".wav"):
file_path = os.path.join(directory, file)
data, sfreq = lr.load(file_path, sr=None)
# Aanpassen lengte bestand naar 30 sec
if len(data) > lengte:
data = data[:lengte]
elif len(data) < lengte:
padding = lengte - len(data)
data = np.pad(data, (0, padding), mode='constant')
# Toevoegen van de data en sample frequenties aan lijsten
audio_data.append(data)
sample_freqs.append(sfreq)
# Aanmaken van de MFCC en de Chroma
mfcc_dict = mfccs(data, sfreq)
# chroma_dict = chroma_features(data, sfreq)
# Updaten van dict met elke MFCC
for key, value in mfcc_dict.items():
if key not in mfcc_data:
mfcc_data[key] = []
mfcc_data[key].append(value)
# Updaten van dict met elke Chroma
# for key, value in chroma_dict.items():
# if key not in chroma_data:
# chroma_data[key] = []
# chroma_data[key].append(value)
# Omzetten van data en frequenties naar np.arrays
audio_data = np.stack(audio_data, axis=0)
sample_freqs = np.array(sample_freqs)
# Maken van dataframe met huidige data
df = pd.DataFrame({
'filename': os.listdir(directory),
'data': audio_data.tolist(),
'Hz': sample_freqs.tolist(),
})
# Toevoegen van alle MFCC's
for key, values in mfcc_data.items():
df[key] = values
# Toevoegen van alle Chroma's
# for key, values in chroma_data.items():
# df[key] = values
# Labels mergen in data indien de directory labeled heet
if directory == "labeled":
df = labeled_data.merge(df, how='left', on='filename')
# Berekenen van spectogrammen en features
# Spectogrammen
spectrograms, spectrograms_db = calculate_spectrograms(
audio_data, n_fft=2048, hop_length=512, win_length=None
)
# Bandbreedte en Centroids
bandwidths, centroids = calculate_spectral_features(
spectrograms
)
# Spectrale contrast
spectral_contrast = calculate_spectral_contrast(
audio_data, sfreq, n_fft=2048, hop_length=512
)
# Tonnetz
tonnetz = calculate_tonnetz(
audio_data, sfreq
)
# Spectrale rolloff
spectral_rolloff = calculate_spectral_rolloff(
audio_data, sfreq, roll_percent=0.85, n_fft=2048, hop_length=512
)
# Root Mean Squared Enerdy
rms = rms_energy_features(
audio_data
)
# Zero Crossing Rate
zcr = zero_crossing_rate_features(
audio_data
)
# Beat en Tempo
beat, tempo = calculate_beat(
audio_data, sfreq
)
# Harmonie
harmonie = calculate_harmonie(
audio_data
)
# Toevoegen van alle features aan dataframe
df['mean_bandwidth'] = [np.mean(arr) for arr in bandwidths]
df['mean_centroids'] = [np.mean(arr) for arr in centroids]
df['mean_spectral_contrast'] = [np.mean(arr) for arr in spectral_contrast]
df['mean_tonnetz'] = [np.mean(arr) for arr in tonnetz]
df['mean_spectral_rolloff'] = [np.mean(arr) for arr in spectral_rolloff]
df['mean_rms_energy'] = [np.mean(arr) for arr in rms]
df['mean_zcr'] = [np.mean(arr) for arr in zcr]
df['mean_beat'] = [np.mean(arr) for arr in beat]
df['mean_tempo'] = [np.mean(arr) for arr in tempo]
df['mean_harmonie'] = [np.mean(arr) for arr in harmonie]
# Tonen van dataframe met index = filename
df = df.set_index('filename')
display(df.head())
return df
Zoals er te zien is de chroma functie veranderd in comments. Deze code is niet meer gebruikt, aangezien dit een klein probleempje opleverde bij het maken en bepalen van clusters. Door de feature niet in het dataframe te zetten is de loop van het clusteren duidelijker en logischer geworden.
Met behulp van de functie kunnen we nu beide datasets inladen in Python. Vervolgens worden deze in combinatie gebruikt om de clusters te bepalen en de genres van de clusters te kunnen definieren.
# Inladen van gelabelde dataset
labeled = load_music("labeled")
| genre | data | Hz | mfcc1_mean | mfcc2_mean | mfcc3_mean | mfcc4_mean | mfcc5_mean | mfcc6_mean | mfcc7_mean | ... | mean_bandwidth | mean_centroids | mean_spectral_contrast | mean_tonnetz | mean_spectral_rolloff | mean_rms_energy | mean_zcr | mean_beat | mean_tempo | mean_harmonie | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| filename | |||||||||||||||||||||
| m00002.wav | jazz | [-0.016357421875, -0.0228271484375, -0.0146789... | 22050 | -298.807953 | 112.078209 | 6.485770 | 28.386517 | -6.764679 | 16.651894 | -11.809684 | ... | 1919.917650 | 1451.498371 | 24.225544 | 0.000351 | 3046.089914 | 0.050493 | 0.051222 | 642.500000 | 129.199219 | 0.000083 |
| m00039.wav | reggae | [-0.09478759765625, -0.15338134765625, -0.1439... | 22050 | -169.243668 | 110.447716 | -8.553957 | 43.898693 | 0.266454 | 26.646509 | -14.365674 | ... | 2019.252686 | 1811.358216 | 22.132186 | 0.016613 | 3854.901690 | 0.123652 | 0.072178 | 623.820896 | 135.999178 | -0.001899 |
| m00041.wav | pop | [0.078033447265625, -0.03765869140625, 0.12664... | 22050 | -18.854591 | 71.328522 | -3.743232 | -1.396592 | 0.710347 | -1.049137 | -1.052407 | ... | 2992.192112 | 3111.061099 | 17.239507 | -0.035761 | 6745.275879 | 0.198348 | 0.152910 | 616.652174 | 95.703125 | 0.000018 |
| m00072.wav | disco | [0.1060791015625, 0.0849609375, 0.062103271484... | 22050 | -69.599335 | 83.059570 | -16.599524 | 0.119469 | 7.415704 | 0.769619 | 1.337008 | ... | 2709.990169 | 2625.095044 | 19.452643 | -0.018575 | 5606.407765 | 0.142975 | 0.120259 | 639.920635 | 129.199219 | -0.000002 |
| m00096.wav | disco | [-0.03607177734375, -0.105682373046875, -0.201... | 22050 | -91.886307 | 87.604057 | -2.058175 | 34.285538 | -18.153370 | 19.344702 | -14.697328 | ... | 2486.020650 | 2550.135384 | 21.882917 | 0.009210 | 5585.291227 | 0.184435 | 0.115890 | 639.274194 | 123.046875 | 0.003067 |
5 rows × 33 columns
# Inladen van de ongelabelde dataset
unlabeled = load_music("unlabeled")
| data | Hz | mfcc1_mean | mfcc2_mean | mfcc3_mean | mfcc4_mean | mfcc5_mean | mfcc6_mean | mfcc7_mean | mfcc8_mean | ... | mean_bandwidth | mean_centroids | mean_spectral_contrast | mean_tonnetz | mean_spectral_rolloff | mean_rms_energy | mean_zcr | mean_beat | mean_tempo | mean_harmonie | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| filename | |||||||||||||||||||||
| m00003.wav | [-0.129364013671875, -0.1422119140625, -0.1157... | 22050 | -82.501259 | 97.344116 | -34.373585 | 71.405922 | -3.283182 | 17.367373 | -9.023832 | 16.011181 | ... | 2070.908729 | 2254.451748 | 20.382411 | 0.005218 | 4381.173508 | 0.136249 | 0.120159 | 636.772727 | 135.999178 | -0.001880 |
| m00012.wav | [-0.003814697265625, 0.089080810546875, 0.1600... | 22050 | -1.925411 | 72.695557 | -32.789642 | 63.592033 | -18.557953 | 25.872955 | -13.854105 | 18.064384 | ... | 2286.246872 | 2908.260266 | 20.043229 | 0.001804 | 5405.642313 | 0.217447 | 0.178119 | 612.180000 | 103.359375 | 0.000014 |
| m00013.wav | [0.004791259765625, 0.0048828125, 0.0045166015... | 22050 | -287.520996 | 101.737930 | -35.368999 | 41.282764 | -12.745123 | 17.284992 | -13.978299 | 16.311886 | ... | 1865.851544 | 1953.012399 | 23.257077 | 0.012233 | 3780.543806 | 0.029100 | 0.123492 | 639.305263 | 184.570312 | -0.000730 |
| m00043.wav | [-0.11505126953125, -0.12127685546875, -0.1372... | 22050 | -120.127808 | 91.287666 | -38.794960 | 75.003784 | -3.806012 | 22.666491 | -8.123723 | 23.654572 | ... | 2073.302486 | 2384.812610 | 19.721734 | 0.014826 | 4585.038942 | 0.100442 | 0.126103 | 639.573529 | 135.999178 | -0.000309 |
| m00044.wav | [-0.017822265625, -0.016693115234375, -0.01486... | 22050 | -437.525208 | 170.971405 | 7.386171 | -3.108705 | 0.777302 | -11.135056 | -12.523807 | -6.900928 | ... | 899.979779 | 790.478225 | 20.256635 | -0.024661 | 1159.401323 | 0.018844 | 0.052112 | 578.258065 | 143.554688 | -0.000012 |
5 rows × 32 columns
Nu de data is ingeladen kunnen we met behulp van de Audio functie een bestand afspelen in het notebook.
# Pick a random audio clip
random_filename = np.random.choice(labeled.index, size=1, replace=False).item()
# Access the data, Hz and genre
clip = np.array(labeled.at[random_filename, 'data'])
sfreq = labeled.at[random_filename, 'Hz']
genre = labeled.at[random_filename, 'genre']
# Tonen van bestandsnaam en het genre
print(f"Bestand en genre: {random_filename}, {genre}")
# Play the clip
Audio(data=clip, rate=sfreq)
Bestand en genre: m00206.wav, hiphop
Nu alle data is ingeladen en de features zijn aangemaakt, kunnen we richting het gebruik van unsupervised learning gaan. Er is namelijk een handeling die eerst nog zal moeten gebeuren, scaling. Scaling is van groot belang bij clustering algoritmes omdat alle data goed moet worden vergeleken met elkaar om clusters te kunnen vormen. Als alle data op andere schalen ligt, is het niet makkelijk om features te interpreteren en zal het model problemen ondervinden bij het maken van de clusters. Hiervoor zal een functie worden aangemaakt.
# Scalen van de data
def scaler(df):
"""
Scaled de data in het dataframe op basis van de Z-score
Parameters:
----------
df : pandas.DataFrame
Het dataframe waarvan de data moet worden gescaled
Returns:
----------
scaled_df : pandas.DataFrame
Het dataframe nadat scaling is toegepast
"""
# Berekenen van gemiddelde en standaard deviatie
gem = df.mean(numeric_only=True)
std = df.std(ddof=0, numeric_only=True)
# Berekenen van de waardes in het dataframe
scaled_df = (df - gem) / std
return scaled_df
Nu de functie is gemaakt kunnen we deze toepassen op de data.
# Toepassen van scaler op unlabeled
scdf = scaler(unlabeled)
# Droppen van data en Hz kolommen omdat deze geen toepassing hebben
# voor Unsupervised learning
scdf = scdf.drop(['data', 'Hz'], axis=1)
# Tonen van de eerste vijf regels
display(scdf.head())
| mean_bandwidth | mean_beat | mean_centroids | mean_harmonie | mean_rms_energy | mean_spectral_contrast | mean_spectral_rolloff | mean_tempo | mean_tonnetz | mean_zcr | ... | mfcc1_mean | mfcc20_mean | mfcc2_mean | mfcc3_mean | mfcc4_mean | mfcc5_mean | mfcc6_mean | mfcc7_mean | mfcc8_mean | mfcc9_mean | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| filename | |||||||||||||||||||||
| m00003.wav | -0.282118 | 0.101179 | -0.118884 | -1.781320 | -0.005254 | 0.078336 | -0.205532 | 0.387660 | -0.332126 | 0.027061 | ... | 0.484487 | -0.564025 | -0.044274 | -0.953633 | 2.103414 | -0.223317 | 0.781341 | -0.632439 | 0.962736 | -1.800265 |
| m00012.wav | 0.019216 | -0.440457 | 0.616770 | 0.310657 | 0.947308 | -0.083214 | 0.293430 | -0.702430 | -0.452162 | 1.407482 | ... | 1.074915 | 2.264119 | -0.669653 | -0.886789 | 1.719003 | -1.585550 | 1.483047 | -1.130237 | 1.147159 | -0.935620 |
| m00013.wav | -0.569065 | 0.156956 | -0.458058 | -0.511490 | -1.262254 | 1.447517 | -0.498065 | 2.009818 | -0.085519 | 0.106458 | ... | -1.017818 | 0.345629 | 0.067205 | -0.995640 | 0.621481 | -1.067151 | 0.774544 | -1.143036 | 0.989746 | -0.158202 |
| m00043.wav | -0.278768 | 0.162864 | 0.027796 | -0.046399 | -0.425318 | -0.236340 | -0.106240 | 0.387660 | 0.005644 | 0.168636 | ... | 0.208774 | -1.208863 | -0.197937 | -1.140219 | 2.280413 | -0.269944 | 1.218515 | -0.539675 | 1.649282 | -1.265518 |
| m00044.wav | -1.920661 | -1.187562 | -1.766122 | 0.281819 | -1.382571 | 0.018429 | -1.774679 | 0.639996 | -1.382611 | -1.593567 | ... | -2.116991 | -1.297411 | 1.823784 | 0.808667 | -1.562392 | 0.138805 | -1.570094 | -0.993139 | -1.095278 | -0.188952 |
5 rows × 30 columns
Met de gescalede data is het mogelijk om te beginnen aan het clusterings process. Dit gebeurt door gebruik te maken van een clustering alogritme, om de keuze voor het algoritme te maken wordt de silhouette score van drie verschillende cluster algoritmes berekend.
# Aanpassen van OMP_NUM_THREADS tegen error
os.environ['OMP_NUM_THREADS'] = '1'
# Kmeans Silhouette
kmeans = KMeans(n_clusters=3, random_state=42, n_init=10)
kmeans_labels = kmeans.fit_predict(scdf)
kmeans_score = silhouette_score(scdf, kmeans_labels)
# Birch Silhouette
birch = Birch(n_clusters=3, threshold=0.01)
birch_labels = birch.fit_predict(scdf)
birch_score = silhouette_score(scdf, birch_labels)
# Guassian Mixture Silhouette
gmm = GaussianMixture(n_components=3, random_state=42)
gmm_labels = gmm.fit_predict(scdf)
gmm_score = silhouette_score(scdf, gmm_labels)
# Uitprinten Silhouette scores
print("KMeans Silhouette Score:", kmeans_score)
print("Birch Silhouette Score:", birch_score)
print("Gaussian Mixture Silhouette Score:", gmm_score)
c:\Users\crazy\anaconda3\Lib\site-packages\sklearn\cluster\_kmeans.py:1436: UserWarning: KMeans is known to have a memory leak on Windows with MKL, when there are less chunks than available threads. You can avoid it by setting the environment variable OMP_NUM_THREADS=1. warnings.warn(
KMeans Silhouette Score: 0.37254592040266094 Birch Silhouette Score: 0.36750634064214 Gaussian Mixture Silhouette Score: 0.2122367556689989
c:\Users\crazy\anaconda3\Lib\site-packages\sklearn\cluster\_kmeans.py:1436: UserWarning: KMeans is known to have a memory leak on Windows with MKL, when there are less chunks than available threads. You can avoid it by setting the environment variable OMP_NUM_THREADS=1. warnings.warn(
K-Means is een unsupervised learning algoritme dat veel gebruikt wordt om datapunten op basis van overeenkomsten binnen features te groeperen in verschillende clusters. Het algoritme doorloopt een iteratief process dat er als volgt uit ziet:
Dit voorbeeld is opgesteld door de stappen te volgen uit het artikel geschreven door Sharma (2023). Hierbij zullen we gebruik maken van de volgende denkbeeldige dataset:
| Kip | Eieren dag 1 | Eieren dag 2 |
|---|---|---|
| A | 1 | 3 |
| B | 2 | 5 |
| C | 2 | 1 |
| D | 5 | 5 |
| E | 4 | 2 |
| F | 3 | 4 |
| G | 1 | 4 |
| H | 5 | 3 |
Stap 1: Kiezen van het aantal clusters (K)
Voor dit voorbeeld nemen we een aantal clusters van 2.
Stap 2: Initialisatie van de centroïden
We beginnen met een k aantal centroïden, in dit geval hebben we een k van 2. Als begin nemen we kippen A en E als centroïden.
Stap 3: Toewijzing van gegevenspunten aan clusters
We berekenen de afstand van elk punt tot beide centra en wijzen de punten toe aan het dichtstbijzijnde cluster. Dit kan door middel van de afstands formules. In dit voorbeeld zullen we gebruik maken van de Euclidische afstand:
$Euclidisch = \sqrt{(x_2 - x_1)^2 + (y_2 - y_1)^2}$
Hierbij zal dag 1 de waarde van $x$ zijn en dag 2 de waarde van $y$.
Dit geeft de volgende berekeningen voor centroïd A:
$Kip A: \sqrt{(1 - 1)^2 + (3 - 3)^2} = \sqrt{0 + 0} = 0$
$Kip B: \sqrt{(1 - 2)^2 + (3 - 5)^2} = \sqrt{1 + 4} = \sqrt{5}$
$Kip C: \sqrt{(1 - 2)^2 + (3 - 1)^2} = \sqrt{1 + 4} = \sqrt{5}$
$Kip D: \sqrt{(1 - 5)^2 + (3 - 5)^2} = \sqrt{16 + 4} = \sqrt{20}$
$Kip E: \sqrt{(1 - 4)^2 + (3 - 2)^2} = \sqrt{9 + 1} = \sqrt{10}$
$Kip F: \sqrt{(1 - 3)^2 + (3 - 4)^2} = \sqrt{4 + 1} = \sqrt{5}$
$Kip G: \sqrt{(1 - 1)^2 + (3 - 4)^2} = \sqrt{0 + 1} = 1$
$Kip H: \sqrt{(1 - 5)^2 + (3 - 3)^2} = \sqrt{16 + 0} = \sqrt{16}$
En de volgende berekeningen voor centroïd E:
$Kip A: \sqrt{(4 - 1)^2 + (2 - 3)^2} = \sqrt{9 + 1} = \sqrt{10}$
$Kip B: \sqrt{(4 - 2)^2 + (2 - 5)^2} = \sqrt{4 + 9} = \sqrt{13}$
$Kip C: \sqrt{(4 - 2)^2 + (2 - 1)^2} = \sqrt{4 + 1} = \sqrt{5}$
$Kip D: \sqrt{(4 - 5)^2 + (2 - 5)^2} = \sqrt{1 + 9} = \sqrt{10}$
$Kip E: \sqrt{(4 - 4)^2 + (2 - 2)^2} = \sqrt{0 + 0} = 0$
$Kip F: \sqrt{(4 - 3)^2 + (2 - 4)^2} = \sqrt{1 + 4} = \sqrt{5}$
$Kip G: \sqrt{(4 - 1)^2 + (2 - 4)^2} = \sqrt{9 + 4} = \sqrt{13}$
$Kip H: \sqrt{(4 - 5)^2 + (2 - 3)^2} = \sqrt{1 + 1} = \sqrt{2}$
Nu de afstanden bepaald zijn, kunnen de kippen aan de twee clusters worden toegewezen:
De kippen C en F zijn willekeurig toegewezen aan clusters omdat de afstand gelijk is.
Stap 4: Berekenen van nieuwe centroïden
Vervolgens berekenen we de nieuwe centroïden. Deze worden berekend door het gemiddelde te nemen van de nieuwe clusters.
Centroïd 1:
Gemiddelde van A, B, F en G, dus $x = \frac{1 + 2 + 3 + 1}{4} = 1.75$ en $y = \frac{3 + 5 + 4 + 4}{4} = 4$
De centroïd is dus $(1.75, 4)$
Centroïd 2:
Gemiddelde van C, D, E en H, dus $x = \frac{2 + 5 + 4 + 5}{4} = 4$ en $y = \frac{1 + 5 + 2 + 3}{4} = 2.75$
De centroïd is dus $(4, 2.75)$
Stap 5: Herhaling
Na stap vier wordt het process van de afstanden herhaald totdat er geen significante verandering meer aan bod komt. In dit voorbeeld doe ik stappen 3 en 4 nog 1 keer, maar dan met gebruik van de Manhattan afstand:
$Manhattan = |x_2 - x_1| + |y_2 - y_1|$
Iteratie 2: Manhattan Editie
Stap 3: Toewijzing van gegevenspunten aan clusters
Het gebruik van de manhattan afstand leid tot de volgende berkeningen:
Voor centroïd 1 (1.75, 4):
$Kip A: |1.75 - 1| + |4 - 3| = 0.75 + 1 = 1.75$
$Kip B: |1.75 - 2| + |4 - 5| = 0.25 + 1 = 1.25$
$Kip C: |1.75 - 2| + |4 - 1| = 0.25 + 3 = 3.25$
$Kip D: |1.75 - 5| + |4 - 5| = 3.25 + 1 = 4.25$
$Kip E: |1.75 - 4| + |4 - 2| = 2.25 + 2 = 4.25$
$Kip F: |1.75 - 3| + |4 - 4| = 1.25 + 0 = 1.25$
$Kip G: |1.75 - 1| + |4 - 4| = 0.75 + 0 = 0.75$
$Kip H: |1.75 - 5| + |4 - 3| = 3.25 + 1 = 4.25$
Voor centroïd 2 (4, 2.75):
$Kip A: |4 - 1| + |2.75 - 3| = 3 + 0.25 = 3.25$
$Kip B: |4 - 2| + |2.75 - 5| = 2 + 2.25 = 4.25$
$Kip C: |4 - 2| + |2.75 - 1| = 2 + 1.75 = 3.75$
$Kip D: |4 - 5| + |2.75 - 5| = 3 + 2.25 = 5.25$
$Kip E: |4 - 4| + |2.75 - 2| = 0 + 0.75 = 0.75$
$Kip F: |4 - 3| + |2.75 - 4| = 1 + 1.25 = 2.25$
$Kip G: |4 - 1| + |2.75 - 4| = 3 + 1.25 = 4.25$
$Kip H: |4 - 5| + |2.75 - 3| = 1 + 0.25 = 1.25$
Deze afstanden maken de volgende clusters:
Stap 4: Berekenen van nieuwe centroïden
Vervolgens berekenen we de nieuwe centroïden. Deze worden berekend door het gemiddelde te nemen van de nieuwe clusters.
Centroïd 1:
Gemiddelde van A, B, C, D, F en G, dus $x = \frac{1 + 2 + 2 + 5 + 3 + 1}{6} = 2.33$ en $y = \frac{3 + 5 + 1 + 5 + 4 + 4}{6} = 3.66$
De centroïd is dus $(2.33, 3.66)$
Centroïd 2:
Gemiddelde van E en H, dus $x = \frac{4 + 5}{2} = 4.5$ en $y = \frac{2 + 3}{2} = 2.5$
De centroïd is dus $(4.5, 2.5)$
Vervolgens word dit process dus herhaald totdat er geen significante veranderingen plaats vinden. Aangezien in deze iteratie 2 kippen van cluster zijn gewisseld worden de afstanden nog minstens 1 maal berekent. Als er door deze afstanden geen nieuwe veranderingen komen zullen de iteraties stoppen.
Nu het duidelijk is hoe dit wordt gedaan, is het tijd om een veel grotere dataset te gebruiken. Gelukkig hebben we hier hulp van python en kunnen we beginnen bij het begin. Het bepalen van het aantal clusters. Om dit te doen voor de dataset zullen we gebruik maken van de elleboog methode. Deze methode berekent de inertia van elke hoeveelheid clusters en aan de hand van een grafiek kan er dan afgelezen worden hoeveel clusters optimaal is. Dit kan doordat de datapunten een denkbeeldige arm gaan vormen, waarbij er een enkel duidelijk buigpunt in zit. Dit buigpunt (de elleboog) is dan het optimaal aantal clusters om te gebruiken. In python kan de inertia gemakkelijk worden bepaald door gebruik van de SKLearn libary. Hiermee kunnen we loopen over een stuk code, waarbij er voor een aangegeven range meerdere modellen worden gemaakt en de inertia van elk model wordt berekent. Deze kunnen we vervolgens plotten met matplotlib om een elleboog te maken.
# Ignore the warning about KMeans memory leak
import warnings
warnings.filterwarnings("ignore", category=UserWarning, module="sklearn")
def elleboog_KMeans(k, data):
"""
Voert de elleboog methode uit voor een gekozen dataframe
en een gekozen aantal k.
Parameters:
----------
k : int
Het aantal k's waarvoor maximaal getest wordt
data : pandas.DataFrame
Het dataframe waarvoor je het aantal clusters wilt bepalen
"""
# Maken van inertia lijst en range voor k
inertia = []
# Loopen over het modelleren
for i in range(1, k):
# Model aanmaken van K-Means
model = KMeans(n_clusters=i, n_init=10)
# Model fitten
model.fit(data)
# Intertia berekenen en toevoegen aan lijst
inertia.append(model.inertia_)
# Plotten van de verschillende inertia
plt.plot(range(1, k), inertia, '-x')
plt.xlabel('Aantal (k) clusters')
plt.ylabel('inertia')
plt.xticks(range(1, k))
plt.show()
# Uitvoeren van KMeans elleboog methode
elleboog_KMeans(11, scdf)
Zoals te zien is in de grafiek is het elleboog punt op k=3. Dit geeft aan dat het optimaal aantal cluster gelijk is aan 3. Minder clusters zal leiden tot een loss aan informatie, terwijl meer clusters zal leiden tot onduidelijke voorspellingen. Nu het aantal clusters bekent is kunnen we dit invoeren om een K-Means model te trainen en de clusters neer te zetten. Omdat deze handeling door het project heen vaker moet gaan gebeuren, is het in een functie gezet.
def clusteren(k, data):
"""
Deze functie voert een KMeans Clustering
uit op basis van k clusters.
Parameters:
----------
k : int
Aantal clusters, van te voren bepaald
met de elleboog methode
data : pandas.DataFrame
De data waarop je het algoritme toepast
Returns:
----------
df : pandas.DataFrame
Het DataFrame waarop clustering is
toegepast.
"""
# Uitvoeren van K-Means om data te groeperen
kmeans = KMeans(n_clusters=k, n_init=10, random_state=42)
# Clusters toevoegen aan dataframe
data['cluster'] = kmeans.fit_predict(data)
# Tonen van hoeveelheid in elk cluster
print(data['cluster'].value_counts())
return data
# Invoeren van aan clusters
k = 3
# Toepassen clusterings functie
scdf = clusteren(k, scdf)
1 40 0 39 2 26 Name: cluster, dtype: int64
In onze data zijn nu de drie clusters geplaatst. Zoals te zien is zijn er 40 audio fragmenten die horen bij cluster 0, 39 die horen bij cluster 2 en 26 die horen bij cluster 1.
Nu de clusters zijn toegevoegd aan de data is het tijd om te bepalen welke cluster behoort to welke genre muziek. Om dit te doen, moeten we eerst de cluster en de genres groeperen op de respectieve groepen. Dit zorgt ervoor dat we de gemiddelde kenmerken van elke groep kunnen vergelijken om te vinden welke groepen bij elkaar horen. Voor de unlabeled dataset is dit gemakkelijk toe te passen.
# Groeperen van data per cluster
scdf_grouped = scdf.groupby('cluster').agg('mean')
# Tonen van het resultaat
display(scdf_grouped)
| mean_bandwidth | mean_beat | mean_centroids | mean_harmonie | mean_rms_energy | mean_spectral_contrast | mean_spectral_rolloff | mean_tempo | mean_tonnetz | mean_zcr | ... | mfcc1_mean | mfcc20_mean | mfcc2_mean | mfcc3_mean | mfcc4_mean | mfcc5_mean | mfcc6_mean | mfcc7_mean | mfcc8_mean | mfcc9_mean | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| cluster | |||||||||||||||||||||
| 0 | -1.093588 | 0.025930 | -1.127912 | 0.021940 | -1.020845 | 0.922238 | -1.128405 | 0.267355 | 0.595745 | -0.963483 | ... | -1.127160 | -0.201491 | 1.133648 | -0.525976 | -0.309505 | -0.204740 | -0.656909 | -0.516017 | -0.677724 | -0.371118 |
| 1 | 1.072204 | -0.025410 | 0.909882 | 0.218999 | 0.798669 | -0.975278 | 0.982941 | -0.374241 | -0.345211 | 0.540537 | ... | 0.657086 | 0.090581 | -0.861932 | 1.011845 | -0.672757 | 0.882764 | -0.286639 | 1.031204 | -0.292967 | 1.002006 |
| 2 | -0.009162 | 0.000196 | 0.292050 | -0.369831 | 0.302546 | 0.117071 | 0.180391 | 0.174722 | -0.362524 | 0.613629 | ... | 0.679839 | 0.162882 | -0.374423 | -0.767721 | 1.499267 | -1.050988 | 1.426347 | -0.812442 | 1.467305 | -0.984872 |
3 rows × 30 columns
Voor de labeled dataset is er iets meer werk dat moet worden gedaan. Dit komt voornamelijk omdat deze data nog niet is gescaled. De data zal dus eerst gescaled worden, vervolgens zullen de genres weer worden toegevoegd aan het gescalede dataframe. Daarna kan de data worden gegrouped.
# Scalen van labeled dataset
sc_label = scaler(labeled)
# Toevoegen genres aan scaled dataset
sc_label['genre'] = labeled['genre']
# Groeperen van de data op genre
scdfl = sc_label.groupby('genre').agg('mean', numeric_only=True)
# Droppen van de Hz kolom
scdfl = scdfl.drop('Hz', axis=1)
# Tonen van de resultaten
display(scdfl)
| mean_bandwidth | mean_beat | mean_centroids | mean_harmonie | mean_rms_energy | mean_spectral_contrast | mean_spectral_rolloff | mean_tempo | mean_tonnetz | mean_zcr | ... | mfcc1_mean | mfcc20_mean | mfcc2_mean | mfcc3_mean | mfcc4_mean | mfcc5_mean | mfcc6_mean | mfcc7_mean | mfcc8_mean | mfcc9_mean | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| genre | |||||||||||||||||||||
| blues | -0.962848 | -0.067569 | -0.901665 | 0.326426 | 0.445559 | 0.491243 | -0.862672 | -1.568523 | 0.015649 | -0.759994 | ... | -0.263798 | -0.505011 | 0.918134 | 0.243213 | 0.556125 | 0.184839 | 0.188702 | 0.039634 | 0.233437 | 0.177360 |
| classical | -1.283423 | -1.396333 | -1.115026 | -0.151545 | -1.175338 | 0.848172 | -1.287750 | 0.401609 | 1.256835 | -0.552573 | ... | -1.532584 | 1.323335 | 1.129616 | -0.713336 | -0.805599 | 0.187766 | -1.129266 | -0.087103 | -1.427242 | 0.067154 |
| country | -0.087529 | -0.188934 | -0.181292 | 0.338447 | 0.254206 | 0.524883 | -0.166793 | -0.069946 | 0.501087 | -0.198584 | ... | 0.264289 | -0.454321 | 0.290693 | 0.339520 | 0.307473 | -0.060878 | -0.226583 | -0.192221 | -0.180159 | -0.517528 |
| disco | 0.696171 | 0.171105 | 0.508099 | 0.960317 | 0.556157 | -0.560642 | 0.576433 | 0.305484 | -0.266149 | 0.148240 | ... | 0.493680 | -0.216592 | -0.543266 | 0.305322 | -0.784593 | 0.532762 | -0.148738 | 0.742088 | -0.372970 | 0.710443 |
| hiphop | 0.171094 | 0.449266 | 0.447543 | 0.283238 | -0.100621 | -0.186977 | 0.449276 | 0.129152 | -0.219113 | 0.465422 | ... | 0.408264 | 0.260917 | -0.543491 | -0.585650 | 0.695698 | -0.667020 | 1.422759 | -0.765019 | 1.277316 | -0.152469 |
| jazz | -0.560886 | 0.319686 | -0.769580 | 0.170312 | -0.766630 | 0.526863 | -0.684459 | -0.006439 | 0.159303 | -0.851891 | ... | -0.712067 | -0.385368 | 0.634218 | 0.095933 | 0.260028 | -0.483867 | -0.147716 | -0.247177 | -0.249819 | 0.062708 |
| metal | -0.073127 | 0.353569 | 0.502345 | -0.902857 | 0.065198 | -0.244557 | 0.266441 | 1.010133 | -0.346046 | 1.122383 | ... | 0.711583 | 0.318826 | -0.433773 | -0.932222 | 1.311821 | -1.029750 | 1.069734 | -0.735267 | 1.189815 | -0.881726 |
| pop | 1.455405 | -0.034044 | 1.288320 | -0.362690 | 1.224541 | -1.145759 | 1.315076 | -0.403529 | -0.474510 | 0.880912 | ... | 0.783727 | -0.151191 | -1.110466 | 0.967487 | -1.477903 | 0.636546 | -1.024685 | 0.956033 | -0.852722 | 0.912750 |
| reggae | 0.423297 | -0.033274 | -0.009959 | -0.757139 | -0.396511 | -0.484485 | 0.153553 | 0.137896 | -0.393277 | -0.536040 | ... | -0.476479 | 0.378747 | -0.205455 | 0.606452 | -0.312523 | 0.886485 | -0.179706 | 0.637009 | 0.191636 | 0.335474 |
| rock | 0.221846 | 0.426527 | 0.231215 | 0.095492 | -0.106560 | 0.231259 | 0.240894 | 0.064162 | -0.233779 | 0.282125 | ... | 0.323387 | -0.569342 | -0.136208 | -0.326718 | 0.249476 | -0.186882 | 0.175498 | -0.347977 | 0.190707 | -0.714166 |
10 rows × 30 columns
Nu de data volledig is gegroepeerd op cluster kan de data van beide datasets worden vergeleken met elkaar. Op deze wijze zullen we bepalen welke genres aanwezig zijn in de unlabeled dataset.
Om de eerste blik op de verschillende genres te werpen, berekenen we voor elke feature de kortste afstand tussen de clusters en de genres. Hierbij maken we gebruik van de volgende afstanden:
# Maken functie minkowski_afstand
def minkowski_afstand(x1, x2, p):
"""
Deze functie berekent de afstand tussen twee punten door middel
van de minkowski formule.
Parameters:
----------
x1 : numpy array of list
Een set aan verschillende data punten
x2 : numpy array of list
Een set aan verschillende data punten
p : int
Parameter nodig voor de minkowski formule
Returns:
----------
minkowski : int or float
De afstand tussen de punten volgens
de minkowski formule
"""
# Omzetten van eventuele losse varaibelen in een lijst
if not isinstance(x1, (list, np.ndarray)):
x1 = [x1]
if not isinstance(x2, (list, np.ndarray)):
x2 = [x2]
# Berekenen van de afstand tussen de punten
afstand = [abs(a - b) ** p for a, b in zip(x1, x2)]
minkowski = sum(afstand) ** (1/p)
return minkowski
# Maken functie manhattan_afstand
def manhattan_afstand(x1, x2):
"""
Deze functie berekent de afstand tussen twee punten door middel
van de manhattan formule.
Parameters:
----------
x1 : numpy array of list
Een set aan verschillende data punten
x2 : numpy array of list
Een set aan verschillende data punten
Returns:
----------
manhattan : int or float
De afstand tussen de punten volgens
de manhattan formule
"""
# Omzetten van eventuele losse varaibelen in een lijst
if not isinstance(x1, (list, np.ndarray)):
x1 = [x1]
if not isinstance(x2, (list, np.ndarray)):
x2 = [x2]
# Berekenen van de afstand tussen de punten
afstand = [abs(a - b) for a, b in zip(x1, x2)]
manhattan = sum(afstand)
return manhattan
# Maken functie euclidische_afstand
def euclidische_afstand(x1, x2):
"""
Deze functie berekent de afstand tussen twee punten door middel
van de euclidische formule.
Parameters:
----------
x1 : numpy array of list
Een set aan verschillende data punten
x2 : numpy array of list
Een set aan verschillende data punten
Returns:
----------
euclidisch : int or float
De afstand tussen de punten volgens
de euclidische formule
"""
# Omzetten van eventuele losse varaibelen in een lijst
if not isinstance(x1, (list, np.ndarray)):
x1 = [x1]
if not isinstance(x2, (list, np.ndarray)):
x2 = [x2]
# Berekenen van de afstand tussen de punten
afstand = [abs(a - b) for a, b in zip(x1, x2)]
euclidisch = np.sqrt(sum(d ** 2 for d in afstand))
return euclidisch
Nu de afstand functies zijn gemaakt is het mogelijk om de genres te bepalen door middel van de afstanden. Dit gaat als volgt in werking:
Omdat dit drie keer moet gebeuren is er een functie gemaakt om dit uit te voeren.
from collections import Counter
def bepaal_genres(data, afstand_func, *args):
"""
Deze functie neemt een afstandsfunctie en bepaald de afstand van
genre naar cluster. Dit gebeurt voor alle clusters en alle genres,
het uiteindelijke antwoord is de meest voorkomende genre voor elk
cluster.
Parameters:
----------
afstand_functie : func
De afstands functie waarmee de afstand word berekend.
Returns:
----------
De meest voorkomende genre (de meeste kleine afstanden) per cluster
"""
# Aanmaken dictionaries
afstand_dfs = {}
afstand = {}
antwoorden = {}
# Stap 1
# For loop om dataframes te maken met afstanden
for col in data.columns:
# Aanmaken dataframe voor kolom met genres en clusters als index en kolom
df_afstand = pd.DataFrame(index=data.index, columns=scdfl.index)
# Stap 2
for x, row1 in data.iterrows():
for y, row2 in scdfl.iterrows():
if args:
# Voor afstands functies met extra argumenten
df_afstand.at[x, y] = afstand_func(row1[col], row2[col], *args)
else:
# Voor afstands functies met alleen a en b
df_afstand.at[x, y] = afstand_func(row1[col], row2[col])
# Vullen van dict met feature afstanden
afstand_dfs[col] = df_afstand
# Stap 3
# For loop voor kiezen juiste afstand
for _, df_afstand in afstand_dfs.items():
for i, row in df_afstand.iterrows():
# Vinden van genre met kleinste afstand
min_genre = min(row.items(), key=lambda x: x[1])[0]
# Aanmaken key voor values als deze nog niet bestaat
if i not in afstand:
afstand[i] = []
# Toevoegen kleinste genre afstand
afstand[i].append(min_genre)
# Stap 4
# For loop om meest voorkomende en op één na meest voorkomende genre te vinden
for key, value in afstand.items():
# Vinden van meest voorkomende en op één na meest voorkomende genres en tellen
genre_1, aantal_1 = Counter(value).most_common(1)[0]
genre_2, aantal_2 = Counter(value).most_common(2)[1]
# Maken resultaten dict
antwoorden[key] = {
'Genre 1': genre_1,
'Aantal 1': aantal_1,
'Genre 2': genre_2,
'Aantal 2': aantal_2
}
# Stap 5
# Printen van de uitkomsten per cluster
print(f"\nGenres volgens {afstand_func.__name__}:")
for key, value in antwoorden.items():
print(f"Cluster {key}: {value}")
return afstand_dfs
# Kijken welke clusters tot welke genre horen
df_euclidisch = bepaal_genres(scdf_grouped, euclidische_afstand)
df_manhattan = bepaal_genres(scdf_grouped, manhattan_afstand)
df_minkowski = bepaal_genres(scdf_grouped, minkowski_afstand, 2)
Genres volgens euclidische_afstand:
Cluster 0: {'Genre 1': 'classical', 'Aantal 1': 6, 'Genre 2': 'reggae', 'Aantal 2': 4}
Cluster 1: {'Genre 1': 'pop', 'Aantal 1': 10, 'Genre 2': 'disco', 'Aantal 2': 7}
Cluster 2: {'Genre 1': 'metal', 'Aantal 1': 12, 'Genre 2': 'hiphop', 'Aantal 2': 9}
Genres volgens manhattan_afstand:
Cluster 0: {'Genre 1': 'classical', 'Aantal 1': 6, 'Genre 2': 'reggae', 'Aantal 2': 4}
Cluster 1: {'Genre 1': 'pop', 'Aantal 1': 10, 'Genre 2': 'disco', 'Aantal 2': 7}
Cluster 2: {'Genre 1': 'metal', 'Aantal 1': 12, 'Genre 2': 'hiphop', 'Aantal 2': 9}
Genres volgens minkowski_afstand:
Cluster 0: {'Genre 1': 'classical', 'Aantal 1': 6, 'Genre 2': 'reggae', 'Aantal 2': 4}
Cluster 1: {'Genre 1': 'pop', 'Aantal 1': 10, 'Genre 2': 'disco', 'Aantal 2': 7}
Cluster 2: {'Genre 1': 'metal', 'Aantal 1': 12, 'Genre 2': 'hiphop', 'Aantal 2': 9}
Genres volgens minkowski_afstand:
Cluster 0: {'Genre 1': 'classical', 'Aantal 1': 6, 'Genre 2': 'reggae', 'Aantal 2': 4}
Cluster 1: {'Genre 1': 'pop', 'Aantal 1': 10, 'Genre 2': 'disco', 'Aantal 2': 7}
Cluster 2: {'Genre 1': 'metal', 'Aantal 1': 12, 'Genre 2': 'hiphop', 'Aantal 2': 9}
Zoals er te zien is in de resultaten, zijn voor elke afstand dezelfde antwoorden gevonden. Deze resultaten leiden tot de volgende conclusies:
Om een beter begrip te krijgen over welke clusters bij welke genres horen gaan we gebruik maken van scatterplots. In deze scatterplots zou het te zien moeten zijn welke factoren overlappen het meest met elkaar overlappen. Dit moet voornamelijk gebeuren voor clusters 0 en 1, cluster 2 is er waarschijnlijk zo uit te halen. Om dit toch met zekerheid te kunnen bevestigen zullen we beginnen met cluster 2. Omdat dit proces meerdere malen herhaald zal moeten worden maken we gebruik van een functie.
def vergelijking_genres(data1, data2, x, y):
"""
Deze functie maakt twee scatterplots met de datapunten
van aangegeven clusters en/of genres.
Parameters:
----------
data1 : pandas.DataFrame
De data uit het geclusterde dataframe
data2 : pandas.DataFrame
De data uit het gelabelde dataframe
x : str
De kolomnaam van feature 1
y : str
De kolomnaam van feature 2
"""
# Maken van de plotsize
plt.figure(figsize=(15, 6))
# Maken eerste scatterplot
sns.scatterplot(data=data1,
x=x,
y=y,
hue='cluster',
palette='viridis')
# Maken tweede scatterplot
sns.scatterplot(data=data2,
x=x,
y=y,
hue='genre')
plt.show()
De eerste vergelijkingen van features die we gaan bekijken is tussen de tempo en de bandbreedte.
# Invoeren welke clusters er getoond moeten worden
clusters = [1]
# Invoeren welke genres er getoond moeten worden
genres = ['pop', 'disco']
# Invoeren van databronnen en kolommen voor de assen
data1 = scdf[scdf['cluster'].isin(clusters)]
data2 = sc_label[sc_label['genre'].isin(genres)]
x = 'mean_tempo'
y = 'mean_bandwidth'
# Uitvoeren van vergelijking
vergelijking_genres(
data1=data1,
data2=data2,
x=x,
y=y
)
Zoals er te zien is overlappen de genres van pop en disco niet goed. De cluster zit voornamelijk in het gedeelte van pop, dus is het goed aan te nemen dat pop het algemene genre is van cluster 1.
Nu zal er gekeken worden naar cluster 0. Hiervoor zullen dezelfde assen worden gebruikt, om deze reden worden x en y niet opnieuw gedefineerd.
# Invoeren welke clusters er getoond moeten worden
clusters = [0]
# Invoeren welke genres er getoond moeten worden
genres = ['classical', 'jazz']
# Invoeren van databronnen en kolommen voor de assen
data1 = scdf[scdf['cluster'].isin(clusters)]
data2 = sc_label[sc_label['genre'].isin(genres)]
# Uitvoeren van vergelijking
vergelijking_genres(
data1=data1,
data2=data2,
x=x,
y=y
)
Zoals er te zien is, is het genre jazz uitgespreid over de gehele grafiek. Het genre classical is echter gefocussed rond het midden van cluster 0. Om deze reden is het aan te nemen dat classical waarschijnlijker is dan jazz.
Nu is het op naar cluster 2. Deze heeft het genre metal of hiphop.
# Invoeren welke clusters er getoond moeten worden
clusters = [2]
# Invoeren welke genres er getoond moeten worden
genres = ['hiphop', 'metal']
# Invoeren van databronnen en kolommen voor de assen
data1 = scdf[scdf['cluster'].isin(clusters)]
data2 = sc_label[sc_label['genre'].isin(genres)]
# Uitvoeren van vergelijking
vergelijking_genres(
data1=data1,
data2=data2,
x=x,
y=y
)
Bij deze grafiek zie je niet heel veel. Het enige dat opvalt is dat metal een uitschieter buiten het cluster heeft. Om verder onderzoek te doen nemen we een kijkje bij andere features, totdat we een duidelijke conclusie kunnen trekken.
# Invoeren van nieuwe x en y
x = 'mean_rms_energy'
y = 'mean_tonnetz'
# Uitvoeren van vergelijking
vergelijking_genres(
data1=data1,
data2=data2,
x=x,
y=y
)
Bij deze grafiek zit hiphop iets meer naar de buitenkant van het cluster toe, terwijl metal wat meer in de kern van het cluster is.
# Invoeren van nieuwe x en y
x = 'mfcc4_mean'
y = 'mean_bandwidth'
# Uitvoeren van vergelijking
vergelijking_genres(
data1=data1,
data2=data2,
x=x,
y=y
)
Bij de verdeling over de bandwidth en de mfcc_4 is de verdeling ook meer geneigd naar metal toe. Hiermee leid ons relatief korte onderzoek naar het genre metal toe. Wij zullen dit nu gaan uploaden naar Kaggle. Hiervoor moet er een csv bestand aangemaakt worden die alleen het genre en de filename bevat. Dit zal gebeuren met behulp van een functie.
def kaggle_upload(data, cluster_map, naam_csv):
"""
Deze functie maakt een dataframe aan en
zet deze om naar een csv. Dit CSV bestand
kan vervolgens geupload worden naar Kaggle.
Parameters:
----------
cluster_map : dict
Een dict met vaste keys 0, 1 en 2. De values
van de dictionary zijn de genres waarin de
clusters zich lijken te bevinden.
naam_csv : str
De naam van het csv bestand.
Returns:
----------
csv
Een csv bestand dat klaar is om op
kaggle geupload te worden.
"""
# Copy maken van scdf
kaggle = data.copy()
# Vervangen van de cluster nummers naar genres
kaggle['cluster'] = kaggle['cluster'].replace(cluster_map)
kaggle_df = pd.DataFrame(kaggle['cluster'].reset_index())
kaggle_df.rename(columns={'cluster': 'genre'}, inplace=True)
kaggle_df.set_index('filename', inplace=True)
kaggle_df.to_csv(f'{naam_csv}.csv')
Met de functie ready to go, kan de eerste csv worden aangemaakt.
# Maken van dictionary voor vervangen cluster nummers
cluster_map = {
0 : 'classical',
1 : 'pop',
2 : 'metal'
}
# Aanmaken csv voor Kaggle
#kaggle_upload(scdf, cluster_map, 'submission_cl_me_po')
De submission op kaggle had een score van 0.98113. Dit is al drie of vier maal zo geweest onder gebruik van kleine aanpassingen in het algoritme.
Principal Component Analysis (PCA) is een methode waarmee je de complexiteit van grote datasets kunt verminderen door ze om te zetten naar een kleiner aantal variabelen, terwijl je toch de belangrijkste informatie uit de oorspronkelijke dataset behoudt. (Jaadi, 2023), (Kumar, 2021), (StatQuest with Josh Starmer, 2018), (Krish Naik, 2018)
Voorbeeld:
Dit voorbeeld is opgesteld door de stappen te volgen uit het artikel geschreven door Nishant Kumar (2020). Hierbij zullen we gebruik maken van de volgende denkbeeldige dataset:
Dit voorbeeld is opgesteld door de stappen te volgen uit het artikel geschreven door Sharma (2023). Hierbij zullen we gebruik maken van de volgende denkbeeldige dataset:
| X1 | X2 | |
|---|---|---|
| 1 | 2.5 | 3.5 |
| 2 | 4.2 | 5.0 |
| 3 | 1.8 | 1.5 |
| 4 | 3.5 | 4.0 |
| 5 | 2.8 | 2.7 |
Stap 1: Het standaardiseren van de dataset
De formule voor het standaardiseren is x - gemiddelde / standaarddeviatie. Berekenen van:
| X1 | X2 | |
|---|---|---|
| 1 | -0.55685013 | 0.13510892 |
| 2 | 1.5010742538568933 | 1.40175507 |
| 3 | -1.40423075 | -1.55375261 |
| 4 | 0.65369363 | 0.55732431 |
| 5 | -0.193687 | -0.54043569 |
Stap 2: De covariantiematrix berekenen
C= 1/n−1 Z^T Z
Daar komt uit:
array(1.25 , 1.16993124, 1.16993124, 1.25 )
Stap 3: Berekenen van eigenvals en eigenvecs
Eigenvals = [0.08006876 2.41993124]
Eigenvecs = [[-0.70710678 -0.70710678] [ 0.70710678 -0.70710678]]
Stap 4: Bereken van PCA
Principal Component 1 (first value)=Standardized X1 Eigenvector 1, Component 1 + Standardized X2 Eigenvector 1, Component 2
Principal Component 1 (first value) = -0.55685013 -0.70710678 + 0.13510892 -0.70710678 = 0.29821606899640385
Principal Component 1 (second value) = 1.5010742538568933 -0.70710678 + 1.40175507 -0.70710678 = -2.052610296082025
En zo verder
Het uitvoeren van een PCA begint bij het standadiseren van de (numerieke)data, zodat elke feature een gemiddelde van 0 en een variantie van 1 heeft. Het is belangrijk om de data te standadiseren omdat de covariantiematrix niet schaalinvariant is en veranderingen in de schaal van verschillende features zullen resulteren in onjuiste ontledingen. In het kopje clustering is een functie aangemaakt die de data scaled. Hieronder laten we het gestandadiseerde dataframe zien.
display(scdf)
| mean_bandwidth | mean_beat | mean_centroids | mean_harmonie | mean_rms_energy | mean_spectral_contrast | mean_spectral_rolloff | mean_tempo | mean_tonnetz | mean_zcr | ... | mfcc20_mean | mfcc2_mean | mfcc3_mean | mfcc4_mean | mfcc5_mean | mfcc6_mean | mfcc7_mean | mfcc8_mean | mfcc9_mean | cluster | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| filename | |||||||||||||||||||||
| m00003.wav | -0.282118 | 0.101179 | -0.118884 | -1.781320 | -0.005254 | 0.078336 | -0.205532 | 0.387660 | -0.332126 | 0.027061 | ... | -0.564025 | -0.044274 | -0.953633 | 2.103414 | -0.223317 | 0.781341 | -0.632439 | 0.962736 | -1.800265 | 2 |
| m00012.wav | 0.019216 | -0.440457 | 0.616770 | 0.310657 | 0.947308 | -0.083214 | 0.293430 | -0.702430 | -0.452162 | 1.407482 | ... | 2.264119 | -0.669653 | -0.886789 | 1.719003 | -1.585550 | 1.483047 | -1.130237 | 1.147159 | -0.935620 | 2 |
| m00013.wav | -0.569065 | 0.156956 | -0.458058 | -0.511490 | -1.262254 | 1.447517 | -0.498065 | 2.009818 | -0.085519 | 0.106458 | ... | 0.345629 | 0.067205 | -0.995640 | 0.621481 | -1.067151 | 0.774544 | -1.143036 | 0.989746 | -0.158202 | 0 |
| m00043.wav | -0.278768 | 0.162864 | 0.027796 | -0.046399 | -0.425318 | -0.236340 | -0.106240 | 0.387660 | 0.005644 | 0.168636 | ... | -1.208863 | -0.197937 | -1.140219 | 2.280413 | -0.269944 | 1.218515 | -0.539675 | 1.649282 | -1.265518 | 2 |
| m00044.wav | -1.920661 | -1.187562 | -1.766122 | 0.281819 | -1.382571 | 0.018429 | -1.774679 | 0.639996 | -1.382611 | -1.593567 | ... | -1.297411 | 1.823784 | 0.808667 | -1.562392 | 0.138805 | -1.570094 | -0.993139 | -1.095278 | -0.188952 | 0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| m00971.wav | -0.957653 | -0.164036 | -0.938974 | 0.294107 | -1.147540 | -0.551006 | -1.008327 | -0.558598 | 0.725921 | -0.653397 | ... | 0.042968 | 1.277592 | -1.274369 | -0.838470 | -0.847983 | -0.982974 | -0.438186 | -1.385951 | -0.626994 | 0 |
| m00973.wav | 1.431593 | 0.172606 | 1.475285 | 0.277186 | 1.444242 | -1.196850 | 1.498087 | 0.160558 | -1.137006 | 1.021128 | ... | 0.745321 | -1.137606 | 1.381635 | -0.765037 | 0.936434 | -0.155219 | 1.402402 | 0.072683 | 0.612714 | 1 |
| m00988.wav | 1.523881 | -0.067850 | 1.778059 | 0.288494 | 1.222169 | -1.318630 | 1.683362 | -0.702430 | -0.606247 | 1.152154 | ... | -1.140236 | -1.588808 | 1.001067 | -1.726364 | 0.406427 | -0.215518 | 0.647399 | -0.092322 | 1.506906 | 1 |
| m00991.wav | -1.215174 | 0.871910 | -1.548642 | 0.290478 | -1.402743 | 1.448930 | -1.437098 | -0.958130 | -0.168995 | -1.781361 | ... | 0.033323 | 1.625074 | 0.395099 | 0.187274 | 0.774719 | 0.146901 | -0.268150 | 0.379999 | 0.147579 | 0 |
| m00995.wav | -1.261066 | 0.140836 | -1.245551 | 0.290723 | -0.821582 | 1.493382 | -1.222552 | 0.922018 | -0.078143 | -1.128302 | ... | -2.117523 | 1.627783 | -0.955432 | -0.219515 | -0.849474 | -0.385089 | -1.030321 | -0.403109 | -1.222124 | 0 |
105 rows × 31 columns
De tweede stap van het uitvoeren van een PCA is het maken van een covariantiematrix. Deze meet de correlatie tussen de features. Als de matrix positief is, betekent dit dat de variabelen samen stijgen of dalen. Als de matrix negatief is, betekennt dit dat waneer de ene variabele stijgt de andere daalt.
cov_mat = scdf.cov()
Na het maken van de covariantiematrix worden de eigenvals en eigenvecs berekend. Eigenvectors is de richting waar de data naar toe is verspreid en eigenvalues is het relatieve belang van deze richtingen.
eigenvals, eigenvecs = np.linalg.eig(cov_mat)
print(f"The magnitudes of the projections are: {eigenvals}")
print(f"The vectors of the projections are: {eigenvecs}")
The magnitudes of the projections are: [8.86043756e+00 9.35376338e+00 2.34731855e+00 1.36317130e+00 1.28129027e+00 1.06001977e+00 9.48542147e-01 8.15875599e-01 7.68595900e-01 7.50606802e-01 5.70388088e-01 4.07530980e-01 3.40763722e-01 2.89878119e-01 2.55401152e-01 2.26614198e-01 2.03728912e-01 1.97284116e-01 1.70387030e-01 1.41963867e-01 1.37662625e-01 9.57582779e-02 8.09942877e-02 1.17186755e-03 4.54723431e-03 8.77722114e-03 6.56831981e-02 2.89981881e-02 3.39860578e-02 4.45701453e-02 4.22743632e-02] The vectors of the projections are: [[-3.20807365e-01 -7.49246134e-02 -2.49008038e-02 -8.13300583e-02 1.70957390e-02 1.97794408e-02 1.63928921e-02 2.87093492e-02 -2.34449831e-02 -5.63540324e-02 -4.46981281e-02 -5.81123661e-02 -3.11770861e-03 -1.03163366e-01 -2.98902536e-02 1.47447075e-01 3.08429816e-02 8.63072200e-02 1.05157004e-01 1.50135528e-01 2.53779309e-02 -1.37958234e-01 2.37649336e-01 1.82041505e-01 7.58140699e-01 -1.75614564e-01 -2.46345918e-02 -5.21277128e-02 1.59053445e-01 -9.07400646e-02 -2.15187220e-01] [ 1.82646938e-02 8.96341470e-03 1.30523254e-01 -1.08907040e-01 3.14971068e-01 7.04202764e-01 4.60423184e-01 -6.10918268e-02 -6.10607114e-02 3.04428182e-01 -1.22860041e-01 5.58130950e-02 7.92530594e-02 -1.20276864e-01 -5.34011736e-02 -2.92908589e-02 -5.86040316e-02 -9.21274786e-02 -2.10853606e-03 -5.40090216e-02 -5.15386146e-02 6.83890648e-03 3.42648189e-03 -5.23606262e-03 6.13095293e-03 3.49399633e-02 3.66164465e-02 2.16480619e-03 2.14696563e-02 -9.57324549e-03 6.72481589e-03] [-2.96093161e-01 -1.37721755e-01 -3.28823400e-03 -8.77270766e-02 6.70050599e-02 3.06530609e-02 -1.54539660e-02 -4.89898120e-02 1.97785180e-02 -1.56525724e-01 -9.24691076e-02 -3.28785621e-02 1.25219922e-01 1.54322130e-02 -1.02961187e-01 4.68150639e-02 -2.19884289e-02 3.53505858e-02 -3.15605730e-02 2.92698675e-02 2.60858331e-03 -1.42952448e-02 -2.19723553e-02 7.49582885e-01 -4.34076540e-01 -1.91360327e-01 9.72912466e-04 1.30429096e-02 1.51016632e-01 1.15216547e-02 -6.87583150e-03] [-5.24164049e-02 8.83067851e-02 5.78650557e-02 -2.57603289e-01 -4.99696830e-01 1.68259737e-01 1.53457767e-01 1.09520730e-01 7.33707720e-01 1.42993804e-02 -3.83166552e-02 8.99557379e-02 -7.21350060e-02 1.69071419e-01 -2.82079529e-02 7.28971428e-02 4.18767383e-03 6.51918656e-02 -4.74984679e-02 -5.56083647e-02 6.11099987e-02 3.35995004e-02 1.56387477e-02 -2.47555257e-03 5.44742831e-03 2.63240206e-02 2.80074666e-02 2.44481329e-02 3.43540448e-02 -1.37445027e-02 -6.78232798e-04] [-2.56802999e-01 -1.22805812e-01 -8.87819914e-02 4.40615226e-02 8.64240387e-03 7.94261139e-02 1.02212681e-01 1.39725267e-01 -1.39566163e-02 -1.18193809e-01 4.50656176e-01 1.47507061e-01 -3.62880071e-01 -1.58841685e-01 3.70514121e-02 -6.12177642e-02 1.68750917e-01 -7.10370153e-02 -7.47909911e-02 -3.09022949e-01 -2.31149077e-01 1.28067322e-02 -2.28993008e-01 8.06233969e-03 1.02683114e-01 -1.44659161e-01 -1.24482808e-01 1.61040440e-01 9.83699278e-02 2.88914411e-01 2.54930575e-01] [ 2.83801176e-01 3.53746116e-02 1.24398040e-01 -2.60263868e-02 7.38945474e-03 -1.15321010e-01 1.52118280e-01 1.77278369e-01 -2.07657459e-02 -7.33970032e-02 4.42856409e-02 3.46908899e-02 7.24873063e-02 2.75743283e-03 -7.20916334e-01 -5.80938092e-02 2.81782848e-01 2.08327437e-01 8.87132069e-02 1.22273661e-02 -2.77296890e-01 -2.59858192e-01 4.81517542e-02 2.94125652e-02 9.33974670e-03 -5.98064876e-02 5.56517069e-02 1.12667958e-02 -1.01929706e-01 -3.79347144e-03 3.27833703e-02] [-3.08722123e-01 -1.14011142e-01 -1.54731486e-02 -8.98760595e-02 3.10418019e-02 3.24438912e-02 1.13825557e-02 -6.40102488e-03 3.14477463e-03 -1.07999096e-01 -6.01655385e-02 -3.25423151e-02 8.96625414e-02 -7.20122916e-02 -7.55842820e-02 9.32929927e-02 -1.74390577e-03 1.03668125e-01 4.71624853e-02 9.15880221e-02 5.76447340e-03 -7.65256698e-02 1.07363992e-01 -5.94664993e-01 -3.37994412e-01 -5.23024078e-01 -1.20555659e-02 -5.92576864e-02 2.07345219e-01 -4.24810582e-03 -1.01621718e-01] [ 1.04537105e-01 -2.52562983e-02 -1.43309849e-02 -2.07006930e-01 -1.90562368e-01 4.37288830e-01 -5.74439923e-01 5.27684572e-01 -2.25965786e-01 -5.54116073e-02 -1.31871236e-01 -3.68524388e-02 -3.43419650e-02 -9.68133167e-02 -2.83212516e-02 -2.70130383e-03 -3.17029084e-02 -5.03691930e-02 -9.27496284e-02 4.69804595e-02 -3.16904974e-02 -3.67751496e-02 -5.39578331e-02 4.19051765e-03 -7.97754149e-03 -1.55381965e-02 2.13823124e-02 5.95174329e-02 -2.91281684e-02 4.03955645e-03 -1.90142440e-02] [ 1.13594104e-01 9.77380254e-02 -1.29292796e-02 2.23581055e-01 4.03322448e-01 -2.38295715e-02 2.49813909e-01 5.44541385e-01 2.49581351e-01 -4.29280882e-01 -1.19286208e-01 -2.12604621e-01 -1.41459779e-03 2.73579223e-02 1.50085991e-01 9.23407137e-02 -7.60370195e-02 -8.88405929e-04 6.21878409e-02 8.09802015e-02 9.97468945e-02 8.24641259e-02 -2.94699987e-02 -4.33571931e-03 -1.96478857e-03 1.22180075e-03 -1.17836284e-01 -5.45064829e-03 -5.96670412e-02 1.14215274e-01 -5.51597598e-02] [-2.12041738e-01 -1.96291784e-01 4.11248515e-02 -7.81368527e-02 1.70171465e-01 2.18202750e-02 -1.17450428e-01 -1.26597951e-01 8.52474434e-02 -2.80039173e-01 -1.84796597e-01 1.99013669e-02 2.43663899e-01 2.01300589e-01 -2.52095529e-01 -1.27879756e-01 -9.05657293e-02 -8.33094584e-02 -2.08278270e-01 -1.51496393e-01 7.24685216e-02 1.98979745e-01 -3.88779326e-01 -1.27956437e-01 2.57056760e-01 -1.97050846e-02 1.30200937e-01 -1.05046490e-01 -1.99395515e-01 -1.84480073e-01 2.95525564e-01] [-1.37767209e-02 -2.88152729e-01 1.55931979e-01 7.05509151e-02 -1.27372911e-01 -7.65948872e-02 7.91208298e-02 1.73979302e-01 -6.28681996e-02 -2.95820244e-02 -2.66027401e-02 1.16486370e-02 1.82755964e-01 -1.35944224e-01 -7.12259099e-03 -2.85174139e-01 -1.29741710e-01 1.25471040e-01 -9.08431228e-02 -4.44430422e-01 -1.93125953e-02 2.95427930e-01 4.90195946e-01 -8.35441387e-03 -1.77870455e-02 7.48125125e-02 -2.60554628e-01 9.47479502e-02 1.18479307e-02 -1.99271378e-01 -3.08553948e-02] [-1.81013198e-01 1.54827956e-01 5.97989960e-02 3.72384192e-01 1.11125390e-01 -1.06846786e-02 -1.91938048e-01 1.25628353e-01 7.85635826e-02 2.22130462e-01 -1.41340887e-01 5.48333952e-01 4.23807257e-02 1.02027263e-01 -7.45064839e-02 3.19810559e-01 -2.61938115e-01 8.66841211e-02 1.43842445e-01 -1.27051305e-01 -6.58493297e-02 -1.75442700e-01 -1.50730725e-02 8.70022992e-03 -1.19209859e-02 -2.04778808e-02 -2.37847481e-01 4.86629928e-02 -7.88650963e-02 -1.03168281e-01 1.22440746e-01] [ 3.25059884e-02 -2.79703802e-01 4.72783807e-02 2.81392535e-01 -1.21256715e-02 -3.04137709e-02 -5.53662140e-02 7.32263678e-02 1.22882640e-01 1.04312731e-01 2.06226526e-01 4.35519944e-03 1.17963746e-01 -1.25762236e-01 -2.35730703e-01 3.66298144e-01 -1.62250196e-02 -3.50668220e-01 -5.99851804e-02 -1.61695911e-02 -4.68418163e-02 2.74113644e-01 -9.17376453e-02 -3.26631938e-02 -1.77440180e-02 1.06826188e-01 3.42966500e-01 1.44533333e-01 1.63109860e-01 -3.16578274e-02 -3.87339885e-01] [-1.14528970e-01 2.25919758e-01 1.38129251e-01 3.32552174e-01 -7.01243365e-02 1.12425738e-01 -1.49769141e-01 -3.81934327e-02 1.72082355e-01 -1.65791387e-02 4.48463428e-02 8.41992272e-02 3.78160608e-01 -3.24125989e-01 4.25597187e-02 -1.75562836e-01 4.40142290e-01 -2.09727793e-01 -9.07040130e-02 1.45136731e-01 1.91795555e-01 -7.28663999e-04 1.91682109e-01 2.50166226e-02 1.79112472e-02 -6.63872649e-02 1.91442891e-02 -1.18198037e-01 -9.36784730e-02 1.85554862e-01 2.03993044e-01] [ 3.05879320e-02 -2.45124696e-01 1.99011380e-01 3.35575180e-01 -2.28504616e-02 1.42798083e-01 -1.11433075e-01 -1.29760260e-01 1.89945519e-01 -2.88920866e-03 -5.42462084e-02 -1.45979420e-01 3.78693748e-02 -2.50750990e-02 2.51733517e-01 -1.14474780e-01 1.40484985e-02 3.89939616e-01 -2.47657786e-01 1.67680775e-01 -5.16042938e-01 -1.43725685e-01 -1.82827975e-01 -8.99035604e-03 1.98323022e-02 5.63314233e-02 -3.10293695e-02 -1.00146027e-01 2.14766515e-02 -2.67876969e-02 -1.40758548e-01] [-7.75953663e-02 2.17906095e-01 2.84092035e-01 2.43082793e-01 -1.06645668e-01 1.75700673e-01 1.55421862e-02 -1.02975226e-01 -8.52466316e-02 -2.57462688e-01 2.74855193e-01 -1.33782099e-01 -2.48840941e-01 -5.44000771e-02 -1.25181468e-01 -2.34642505e-02 -3.59173166e-01 1.48429939e-01 -1.48723048e-02 2.06101934e-01 1.44905601e-01 3.35978286e-03 1.59194675e-01 -1.60532341e-03 -2.92559867e-02 1.50392502e-02 2.83581259e-01 2.29929700e-01 3.41747461e-02 -2.51089418e-01 2.58529174e-01] [ 8.26728555e-02 -1.57396476e-01 4.47541086e-01 1.21463568e-01 -7.96378047e-02 1.38683039e-01 -1.03026011e-01 -1.80758200e-01 -7.55599178e-02 -6.51256878e-02 -1.75980220e-01 -2.53763123e-01 -2.43130134e-01 1.55390947e-01 -1.03086528e-02 3.09139550e-01 3.63844419e-01 -1.25107112e-02 3.32791910e-01 -1.97442784e-01 1.73241375e-01 1.15679753e-01 -6.91732395e-02 -2.90189819e-03 -7.00030248e-03 -2.35456412e-02 -2.45825459e-01 -3.86806145e-02 3.37785963e-02 -1.61530147e-03 7.81027850e-02] [-4.00477399e-02 2.17446088e-01 3.60352675e-01 -1.05502170e-01 -1.79455773e-01 1.01144874e-02 1.03309227e-01 -7.62951551e-02 -1.77492175e-01 -2.73551770e-01 2.03785914e-01 -4.67395250e-04 2.17589475e-01 3.56450770e-02 8.00470628e-02 1.20026256e-01 -2.20530399e-01 -1.93043077e-01 -1.45324677e-01 -2.68597321e-01 -2.39308790e-02 -3.09752817e-01 -9.27423295e-02 3.91964533e-03 4.24831157e-03 -3.76004927e-02 -9.42696901e-02 -1.53700119e-01 -2.50734076e-01 1.67908853e-01 -3.67060317e-01] [ 1.92158650e-02 -9.50987766e-02 4.78060631e-01 -2.28936644e-01 2.11083867e-03 -2.73171396e-01 1.49891232e-01 1.06051860e-01 -9.77023896e-02 -1.00177551e-01 -2.87326793e-01 4.40532488e-01 -2.30375309e-01 -2.10032392e-01 1.68104737e-01 -1.07812639e-03 6.87681779e-02 -1.62708369e-01 -1.27109738e-01 2.85175829e-01 -1.19662573e-01 1.14928991e-01 -1.80868870e-02 1.08288490e-03 -1.13804280e-02 6.59717481e-04 1.30161633e-01 -2.16497096e-03 3.34302362e-02 -4.21558658e-02 5.59039362e-02] [-1.14330885e-01 1.88591045e-01 2.54539488e-01 -2.59588008e-01 1.43802858e-01 -1.07172565e-01 -3.26727357e-03 2.21187526e-01 -1.05718919e-01 2.41277931e-01 3.58129282e-01 -6.65541795e-02 3.43215082e-01 1.22476109e-01 1.47480444e-01 2.33003344e-01 1.95915151e-01 3.87395819e-01 -1.24632470e-01 5.40958932e-02 6.60650070e-02 2.15670099e-01 -1.50806983e-01 -1.19059628e-02 8.05054960e-03 7.37492854e-02 -1.32313071e-02 9.30355901e-02 1.10504521e-01 -1.02652624e-01 9.69862688e-02] [-2.22108869e-01 -1.98646889e-01 -9.92553671e-02 -2.30955811e-02 1.03209574e-01 1.49366274e-01 5.68339252e-04 8.47534464e-02 -1.37623928e-02 -1.50061934e-01 2.95937320e-01 2.29139471e-01 -1.97424576e-01 7.37413655e-02 -6.93220546e-03 -1.68332995e-02 2.36690469e-01 6.35424128e-02 5.80209591e-02 9.96004056e-02 1.46042863e-01 -8.21993241e-02 1.09005378e-01 -4.05371134e-02 -1.73051527e-01 3.67377265e-01 2.20507012e-03 -4.47574003e-01 -2.04971136e-01 -3.14612498e-01 -1.78736729e-01] [-8.02229307e-02 -5.87584805e-02 3.47335730e-01 -1.87495912e-01 4.07749385e-01 -1.52561296e-01 -3.21899266e-01 4.05928172e-02 3.47573261e-01 3.20681852e-01 1.57466692e-01 -2.10454854e-01 -2.02213796e-01 -4.76188450e-02 -8.07782169e-02 -2.71760259e-01 -1.62738736e-01 -1.54632625e-01 6.90988259e-02 -6.41400368e-02 6.57772902e-02 -1.85144721e-01 8.93258346e-02 8.52054410e-03 -2.15516148e-02 -4.46472442e-02 9.48690044e-03 -3.64199837e-02 -7.45921503e-02 6.82480942e-02 -5.66479955e-02] [ 2.84192361e-01 1.51272691e-01 -3.38504646e-02 9.07371766e-02 -2.30905954e-02 3.50721110e-04 9.26529564e-03 7.21734587e-02 2.51950604e-02 1.29261101e-01 1.17840023e-01 3.68793812e-02 -1.25597890e-01 -8.61588120e-02 1.27890572e-01 -4.51746695e-03 -8.14549954e-03 4.07411805e-02 2.04096713e-02 -1.93293397e-01 -3.84936633e-02 2.35631987e-01 -8.06483966e-02 1.64083362e-01 4.08982661e-02 -5.79578542e-01 1.95105179e-01 -4.21251705e-01 -1.36264817e-01 -3.26406348e-01 -2.88251677e-02] [-2.60672428e-01 1.11209751e-01 -7.68917512e-02 -5.95322722e-02 -1.54036296e-01 -1.50662719e-01 9.62583094e-02 6.57010399e-02 1.44775109e-03 1.42121723e-01 -1.52714212e-01 -3.66204486e-01 -5.91419775e-02 -5.47205754e-01 -1.28700772e-01 2.65068535e-01 -1.36142661e-01 2.38797629e-03 2.57447024e-02 -3.36463121e-02 -1.60861126e-01 4.06572130e-02 -1.20154605e-01 -2.69928787e-02 -7.82568709e-02 2.35690310e-01 -8.97118278e-02 -2.67973526e-01 -1.80163834e-01 -7.20185668e-02 2.01553331e-01] [ 1.22924056e-01 -2.62998032e-01 2.42023320e-02 6.59482879e-02 -1.46625244e-01 -3.21765937e-02 1.54473247e-01 1.50428068e-01 -5.16686914e-02 1.61548087e-01 1.94205582e-01 -1.48706179e-01 8.93173886e-02 2.66593377e-01 -1.27112897e-02 8.52851664e-02 -1.13630749e-01 -4.02814509e-01 -2.12665739e-01 3.08634632e-01 -3.05522853e-02 -1.87298160e-01 6.62505953e-02 2.06601369e-02 1.88167980e-02 -1.17016800e-01 -3.35170773e-01 -1.75970011e-01 1.15211522e-01 -1.70320231e-01 3.32509007e-01] [-2.17683299e-01 1.63744106e-01 1.30424452e-02 2.49559305e-01 -9.27834371e-02 -8.09994913e-02 1.50310282e-01 1.51006050e-01 -1.32417628e-01 2.45011209e-01 -2.15474287e-01 -1.01382213e-01 -2.64093248e-01 7.87391154e-02 -1.60423873e-01 -2.22149169e-01 1.69177280e-01 2.50705486e-02 -4.31023900e-01 -2.80164394e-02 3.26576880e-01 -8.03831771e-02 -2.36668971e-01 -5.41366437e-05 -2.01220041e-02 -5.83329086e-02 -1.02640302e-01 1.23559821e-01 1.94419562e-02 -1.34634483e-01 -2.90562136e-01] [ 1.35662224e-02 -2.86723805e-01 5.95038297e-02 8.76090834e-02 -1.72701149e-01 -8.14940785e-02 1.52293081e-01 2.14087643e-01 -3.73848368e-02 7.40389975e-02 -2.03229019e-02 4.43221225e-03 2.09480179e-01 -1.42011561e-01 2.10155265e-01 -1.77755940e-01 -3.02992911e-02 6.42612694e-02 3.77532457e-01 -1.57086055e-01 2.65351507e-01 -4.23638141e-01 -2.97331148e-01 6.45789307e-03 1.28893247e-03 4.17750415e-02 3.08999519e-01 3.05966033e-02 1.40401741e-01 -1.10351434e-01 1.14610957e-01] [-2.80720321e-01 1.06780533e-01 1.91565662e-02 9.65137055e-02 -8.34684098e-03 -4.71387412e-02 4.55272072e-02 1.80425311e-01 -1.23488889e-01 9.87909802e-02 -1.77726857e-01 -1.69930895e-01 -5.20092100e-02 4.18394235e-01 1.37665438e-01 5.53386952e-02 1.41615367e-01 -1.34193081e-01 -3.77829326e-02 -2.18940209e-01 -3.09406151e-01 -7.05596633e-02 3.22768133e-01 -2.17603008e-02 -2.95775622e-02 5.90623100e-02 4.89094661e-01 -9.94284579e-02 -3.55418948e-02 9.65852663e-02 1.45277835e-01] [ 9.44775661e-03 -3.02221364e-01 6.01036207e-02 9.04975788e-02 -1.10725179e-01 5.77999181e-03 6.85857773e-02 6.87141911e-02 -1.08336477e-01 1.74317081e-01 1.80899887e-02 6.82376294e-02 -6.07466829e-02 7.03688538e-02 -1.23452969e-01 6.23465688e-02 -2.25957654e-01 3.16287872e-01 -8.36908409e-02 1.34088993e-01 2.89161798e-01 1.60792926e-01 6.35020340e-02 3.07036762e-02 4.71344584e-02 -6.36189807e-02 1.22817853e-01 -2.66749367e-01 -2.15467426e-01 6.06383787e-01 5.49186993e-02] [-2.58146128e-01 1.38493758e-01 9.87174563e-02 7.82108526e-02 -1.94738406e-01 2.15439427e-02 5.36005521e-02 1.14609356e-01 -9.44838335e-02 7.60993823e-02 6.47541451e-02 -1.30247967e-02 8.26561204e-02 1.87052953e-01 -7.84482413e-02 -3.87839396e-01 -9.91064201e-02 -1.28599231e-01 5.03335143e-01 2.70887898e-01 -2.34708579e-01 3.52142355e-01 -1.99903357e-01 3.82682105e-03 5.02729670e-03 -1.04645239e-02 -1.34000599e-01 -4.38683119e-02 -4.86142881e-02 5.16035844e-02 -1.71028318e-01] [-8.41142044e-02 -2.25165579e-01 -6.89364087e-02 -4.04152719e-02 -3.40429422e-02 6.13444138e-03 8.22445406e-02 -6.54018854e-03 2.19592416e-02 8.85838993e-02 3.42838707e-02 -7.04763527e-02 2.29270263e-02 -1.09863876e-02 1.50744382e-01 1.12488672e-01 1.22420323e-01 -2.02314672e-02 6.10429970e-02 1.46092951e-01 -2.63681160e-02 -3.63334578e-02 5.37222004e-02 2.51300569e-02 -3.19492777e-02 -2.10688052e-01 1.21485335e-02 4.77914112e-01 -7.35051566e-01 -1.27759378e-01 -2.75538529e-02]]
inds = eigenvals.argsort()
eigenvecs = eigenvecs[inds[::-1]]
eigenvals = eigenvals[inds[::-1]]
plt.plot(eigenvals, '-o')
[<matplotlib.lines.Line2D at 0x184019d3750>]
Aan de hand van deze plot zien we dat de elleboog bij 3 ligt. Daarom kiezen wij voor 3 pca componenten.
Stap 4 is het uitvoeren van de PCA op de gestandaardiseerde data. We fitten de data en transformen deze.
pca = PCA()
pca.fit(scdf)
transformed = pca.transform(scdf)
print(transformed.shape)
(105, 31)
Hier nog een plot om te laten zien dat de 3 eerste componenten het belangrijkste zijn.
per_var = np.round(pca.explained_variance_ratio_*100, decimals=1)
labels = ['PC' + str(x) for x in range (1, len(per_var)+1)]
plt.bar(x=range(1, len(per_var)+1), height=per_var, tick_label = labels)
plt.ylabel('Percentage of Explained Variance')
plt.xlabel('Principal Component')
plt.title('Scree plot')
plt.show
<function matplotlib.pyplot.show(close=None, block=None)>
xs = transformed[:,0]
ys = transformed[:,1]
plt.scatter(xs, ys)
plt.show()
Bij deze scatterplot kun je goed zien dat er waarschijnlijk 3 verschillende genres zijn. Ik zeg waarschijnlijk omdat je wel kunt zien dat er drie groepen zijn, maar of deze datapunten ook echt allemaal bij elkaar horen kunnen we straks in een andere scatterplot beter zien.
unlabeled2 = unlabeled.copy()
#PCA componenten aanmaken
unlabeled2['PCA_Component_1'] = transformed[:, 0]
unlabeled2['PCA_Component_2'] = transformed[:, 1]
unlabeled2['PCA_Component_3'] = transformed[:, 2]
Het dataframe met de filenames en de pca componenten
unlabeled2= unlabeled2[['PCA_Component_1', 'PCA_Component_2', 'PCA_Component_3']]
unlabeled2
| PCA_Component_1 | PCA_Component_2 | PCA_Component_3 | |
|---|---|---|---|
| filename | |||
| m00003.wav | 4.235612 | -2.055890 | -0.720285 |
| m00012.wav | 6.136067 | -0.594984 | 0.646706 |
| m00013.wav | 1.177170 | -2.997248 | 3.263309 |
| m00043.wav | 4.000373 | -1.748447 | -2.114490 |
| m00044.wav | -4.145249 | -3.360042 | -3.709625 |
| ... | ... | ... | ... |
| m00971.wav | -3.063247 | -2.178624 | -2.070839 |
| m00973.wav | -0.396747 | 4.693099 | -0.285384 |
| m00988.wav | -0.097874 | 4.645286 | -1.303385 |
| m00991.wav | -2.335465 | -3.013813 | 2.475883 |
| m00995.wav | -1.469824 | -4.060340 | -0.940655 |
105 rows × 3 columns
elleboog_KMeans(11, unlabeled2)
Aan de hand van deze grafiek kun je zien dat er 3 clusters zijn. Nu gebruiken we de functie clusteren die in het hoofdstuk clusteren is aangemaakt.
df = clusteren(3, unlabeled2)
0 40 2 39 1 26 Name: cluster, dtype: int64
We groeperen de data per cluster op het gemiddelde
# Groeperen van data per cluster
df_grouped = df.groupby('cluster').agg('mean')
# Tonen van het resultaat
display(df_grouped)
| PCA_Component_1 | PCA_Component_2 | PCA_Component_3 | |
|---|---|---|---|
| cluster | |||
| 0 | -0.881684 | 3.475096 | -0.164351 |
| 1 | 4.890509 | -1.182574 | -0.197208 |
| 2 | -2.356049 | -2.775819 | 0.300037 |
sns.scatterplot(data=df,
x= xs,
y = ys,
hue='cluster',
palette= 'Set1')
plt.show()
Hier kun je duidelijk zien dat de drie groepen die we al eerder hadden gezien ook daadwerkelijk de drie verschillende genres zijn. De datapunten per cluster liggen allemaal dicht bij elkaar
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')
# Scatter plot with 3D projection
scatter = ax.scatter(df['PCA_Component_1'], df['PCA_Component_2'], df['PCA_Component_3'], c=df['cluster'], cmap='viridis')
# Add labels and a color bar
ax.set_xlabel('PCA_Component_1')
ax.set_ylabel('PCA_Component_2')
ax.set_zlabel('PCA_Component_3')
ax.set_title('3D Scatter Plot with Clusters')
fig.colorbar(scatter, label='Cluster')
plt.show()
Ook hebben we een 3demensionale scatterplot gemaakt omdat we met 3 componenten werken. Ook hier zie je hetzelfde als bij de 2 demensionale scatterplot, dat de datapunten per cluster dicht bij elkaar liggen.
Om te kijken welke genre er bij de clusters horen, gaan we kijken naar de afstanden. We beginnen met de gelabelde data scalen.
sc_label.drop(['Hz', 'data', 'genre'], axis=1, inplace=True)
Hier passen we de PCA op toe
pca = PCA(n_components=3)
pca.fit(sc_label)
transformed_l = pca.transform(sc_label)
print(transformed_l.shape)
(50, 3)
Nu maken we features van de pca data en zetten deze in een dataframe van de gelabelde data.
label = sc_label.copy()
label['genre'] = labeled['genre']
label['PCA_Component_1'] = transformed_l[:, 0]
label['PCA_Component_2'] = transformed_l[:, 1]
label['PCA_Component_3'] = transformed_l[:, 2]
# Print de resulterende DataFrame
print(label)
mean_bandwidth mean_beat mean_centroids mean_harmonie \
filename
m00002.wav -0.610133 0.359610 -1.092007 0.410972
m00039.wav -0.411385 -0.067294 -0.573296 -1.642859
m00041.wav 1.535260 -0.231133 1.300127 0.343474
m00072.wav 0.970634 0.300659 0.599644 0.322589
m00096.wav 0.522519 0.285885 0.491595 3.504310
m00102.wav -1.414572 0.297681 -1.492100 -0.629824
m00112.wav 0.740998 0.312225 -0.137938 -3.127108
m00138.wav 0.141260 0.287360 -0.307725 0.328948
m00192.wav -0.519875 -5.888664 -0.476122 -0.271121
m00206.wav 0.141239 0.496945 0.759211 0.300667
m00230.wav 1.657057 0.424746 2.236136 0.325323
m00236.wav -1.619458 0.447912 -1.268198 0.308425
m00248.wav 0.224815 0.178713 0.644430 -1.496280
m00253.wav 0.267146 0.061034 0.337512 0.343280
m00298.wav -0.502545 -0.251752 -0.564401 0.322885
m00313.wav -1.523249 0.148585 -1.584653 0.317997
m00338.wav -0.595084 0.224581 -0.334379 0.321253
m00339.wav -0.245430 0.299309 0.377963 0.325102
m00351.wav -0.233972 -0.386570 -0.552227 0.345841
m00400.wav -2.460509 -0.520294 -2.362404 0.326717
m00421.wav 1.998793 0.130544 2.188316 0.320502
m00429.wav 0.204828 1.099529 0.091887 0.312079
m00435.wav -1.347296 0.034016 -0.843604 -0.399708
m00454.wav 0.343704 0.057339 0.664594 0.307062
m00477.wav -1.515913 -1.872608 -1.495107 0.234502
m00501.wav -0.267606 1.247700 -0.696861 -0.434746
m00503.wav -0.375276 0.540452 0.366644 -1.300940
m00513.wav 2.403656 0.198521 2.657701 -3.139432
m00553.wav -0.161993 -0.095679 -0.131943 0.317907
m00606.wav -0.529961 -1.866178 -1.065649 0.324885
m00623.wav 1.498992 -0.614455 1.264272 0.323540
m00627.wav -0.264953 -0.083731 0.244040 -2.368593
m00629.wav -0.217338 0.286651 -0.472680 0.382821
m00633.wav -0.278625 0.129487 -0.474444 0.331835
m00637.wav -0.086584 0.293243 0.116784 0.220553
m00658.wav 0.252283 0.299274 0.605239 0.275826
m00671.wav 0.146621 -0.084203 -0.295108 0.331784
m00676.wav 0.669594 -0.205831 0.519156 0.333844
m00677.wav -1.068777 0.080626 -1.129823 0.327368
m00678.wav -0.218363 0.943969 0.046656 0.319453
m00716.wav -0.280426 0.053313 -0.251540 0.369363
m00762.wav 1.532382 0.147893 1.010290 0.333203
m00772.wav -0.596171 0.138500 -0.190153 0.325062
m00773.wav 0.669723 -0.062322 -0.223699 0.328163
m00801.wav 1.957072 0.371786 1.063803 -0.981344
m00821.wav 0.268142 0.188443 1.209958 0.332074
m00850.wav 0.617315 0.216768 0.570907 0.323577
m00867.wav -0.313402 1.116556 -0.292722 0.325932
m00895.wav 0.307163 0.206484 0.197185 0.482706
m00996.wav -1.412294 0.324376 -1.255266 0.160130
mean_rms_energy mean_spectral_contrast mean_spectral_rolloff \
filename
m00002.wav -1.148479 1.885706 -1.009321
m00039.wav 0.143471 0.858330 -0.468786
m00041.wav 1.462562 -1.542893 1.462870
m00072.wav 0.484712 -0.456732 0.701757
m00096.wav 1.216862 0.735994 0.687644
m00102.wav -1.872493 0.590103 -1.652879
m00112.wav -1.027873 -1.288618 0.153426
m00138.wav -0.655435 0.504976 0.000897
m00192.wav -0.007500 -0.731520 -0.616370
m00206.wav -0.212702 -0.472698 0.577007
m00230.wav -0.339340 -0.687443 1.928927
m00236.wav -1.664066 1.575199 -1.399621
m00248.wav -0.031329 -1.004730 0.514270
m00253.wav 2.741119 0.687510 0.475302
m00298.wav -0.604069 0.380307 -0.408851
m00313.wav -0.465289 0.314094 -1.545533
m00338.wav 1.432591 0.572837 -0.409993
m00339.wav -0.152732 0.886229 0.087414
m00351.wav -0.783332 0.961884 -0.364136
m00400.wav -0.876555 0.501466 -2.424284
m00421.wav 2.406325 -1.032669 2.056261
m00429.wav -0.113470 -0.062799 0.273130
m00435.wav -0.583478 1.674181 -1.219162
m00454.wav 0.091488 0.190610 0.648727
m00477.wav -1.749154 1.132896 -1.550718
m00501.wav -0.313032 -0.035933 -0.702820
m00503.wav -0.386350 0.277942 -0.005864
m00513.wav 0.722845 -1.768020 2.556876
m00553.wav 0.554915 -0.875933 -0.238857
m00606.wav -0.473707 -0.801763 -1.062261
m00623.wav 0.483956 -1.179928 1.347176
m00627.wav 0.283654 -0.417977 0.128453
m00629.wav 0.696643 0.778216 -0.297322
m00633.wav 0.317514 0.866001 -0.350447
m00637.wav -0.233907 -0.393514 0.175869
m00658.wav -0.034514 -0.196482 0.571649
m00671.wav -0.926675 -1.317186 -0.264949
m00676.wav 2.100149 -2.045880 0.531674
m00677.wav 1.069921 2.469404 -1.052861
m00678.wav 0.414517 0.142417 -0.043891
m00716.wav -1.000883 0.490734 -0.142621
m00762.wav 0.001160 -1.393168 1.199475
m00772.wav -0.599449 0.949102 -0.351265
m00773.wav -0.569178 0.660665 -0.032301
m00801.wav -0.972810 -0.717789 1.621881
m00821.wav 0.045498 -0.220436 0.739237
m00850.wav 0.523135 -0.813370 0.532147
m00867.wav -0.690522 1.121854 -0.273464
m00895.wav 1.882714 -1.083102 0.119905
m00996.wav -0.587426 -0.668079 -1.203396
mean_tempo mean_tonnetz mean_zcr ... mfcc4_mean mfcc5_mean \
filename ...
m00002.wav 0.556581 -0.201259 -1.393183 ... -0.414782 -0.455867
m00039.wav 0.862029 0.548655 -0.874438 ... 0.416474 0.080578
m00041.wav -0.948033 -1.866566 1.124088 ... -2.010779 0.114446
m00072.wav 0.556581 -1.074005 0.315799 ... -1.929537 0.626036
m00096.wav 0.280223 0.207270 0.207668 ... -0.098669 -1.324775
m00102.wav 0.280223 1.795694 -1.019308 ... -0.854354 0.301705
m00112.wav 1.580730 -2.073776 -1.154037 ... 0.028416 2.184078
m00138.wav 0.280223 0.137251 -0.920355 ... -0.285322 -0.192336
m00192.wav 0.556581 -0.692819 0.101782 ... -0.849080 -0.276803
m00206.wav -0.200399 -0.071087 1.240265 ... 0.989720 -0.946793
m00230.wav 0.280223 -0.492165 2.596123 ... 0.073349 0.762104
m00236.wav 0.280223 0.962270 -0.710080 ... -0.992708 0.237479
m00248.wav -0.410671 -0.477346 1.269286 ... 1.808582 -0.835496
m00253.wav -1.729651 -0.427638 0.426625 ... 0.485522 -0.769230
m00298.wav -1.619736 0.704218 -0.700220 ... 0.155711 -1.492965
m00313.wav -1.101566 -1.621150 -1.380740 ... 1.637573 1.425775
m00338.wav -0.782691 0.321627 -0.021645 ... 1.542112 -0.486405
m00339.wav 0.556581 -0.308531 1.075439 ... 0.580677 -1.169200
m00351.wav -0.782691 1.201644 -0.757026 ... 0.185749 -0.640877
m00400.wav -2.608971 1.101189 -2.123989 ... -1.040294 2.247019
m00421.wav -1.244509 -0.065184 0.923010 ... -0.455135 1.301029
m00429.wav 1.580730 -0.006813 -0.074261 ... 0.066440 -0.178251
m00435.wav 0.862029 2.557347 0.018733 ... -1.225742 -0.068556
m00454.wav 0.280223 0.450457 0.250582 ... 0.829423 -0.479910
m00477.wav 0.028989 1.661682 -1.153990 ... -0.106110 0.745008
m00501.wav 1.580730 0.166343 -0.989679 ... -0.303797 0.357383
m00503.wav 0.556581 0.046058 1.096817 ... 0.831167 -0.363833
m00513.wav 0.556581 0.746311 2.555024 ... -1.817327 1.071247
m00553.wav 0.028989 -0.064967 -0.095030 ... -1.059323 1.589195
m00606.wav -1.244509 0.299833 -1.080437 ... -0.406580 0.266873
m00623.wav -0.200399 -0.303472 0.707337 ... -1.526994 1.036150
m00627.wav 0.280223 -0.543171 0.837107 ... 1.018362 -1.721201
m00629.wav 0.556581 -0.565524 -0.653461 ... 0.208406 -1.084738
m00633.wav 0.028989 -0.028764 -0.722448 ... 0.948581 -0.144053
m00637.wav -0.410671 -0.634733 0.141796 ... 0.570878 -1.160373
m00658.wav -0.604122 -0.833389 0.768728 ... 1.022027 -0.569775
m00671.wav -1.833101 -0.275046 -0.438705 ... -0.195188 1.323956
m00676.wav 0.028989 -0.610395 0.597952 ... -1.662574 -0.162367
m00677.wav 0.028989 3.292053 -1.132697 ... 0.713612 -0.104577
m00678.wav 1.580730 -0.482588 0.204562 ... 0.893272 -0.170086
m00716.wav -0.604122 0.150028 -0.352859 ... 0.416435 -1.028670
m00762.wav -0.200399 -0.065397 -0.154784 ... -0.264746 1.971791
m00772.wav 0.556581 0.618857 0.354559 ... 0.895781 -0.969128
m00773.wav -0.410671 -0.576718 -0.795515 ... -1.443701 0.858373
m00801.wav 0.280223 -2.012457 0.026452 ... -0.878657 0.960988
m00821.wav 3.043800 -0.273182 2.204145 ... 2.007721 -2.058134
m00850.wav 0.862029 -0.333645 0.467546 ... -0.570692 -0.198435
m00867.wav 0.028989 -0.226568 -0.243652 ... 0.695981 -0.580285
m00895.wav -1.101566 0.759804 0.197826 ... -0.046403 0.823216
m00996.wav -0.782691 -0.520240 -0.766709 ... 1.416535 -0.651306
mfcc6_mean mfcc7_mean mfcc8_mean mfcc9_mean genre \
filename
m00002.wav 0.173563 -0.548355 0.153848 -0.154394 jazz
m00039.wav 1.095720 -0.798675 0.182794 -0.490289 reggae
m00041.wav -1.459630 0.505158 -1.189411 0.923385 pop
m00072.wav -1.291822 0.739165 -0.678809 0.677718 disco
m00096.wav 0.422015 -0.831156 0.605457 -0.993900 disco
m00102.wav -1.321644 -0.031352 -1.639415 -0.359739 classical
m00112.wav -0.247635 2.078666 0.398821 1.636401 reggae
m00138.wav 0.508027 0.056130 1.740265 -0.534648 reggae
m00192.wav -0.724115 -0.464254 -1.331074 -0.036760 classical
m00206.wav 1.742479 -0.575393 1.731972 -0.589271 hiphop
m00230.wav -1.017711 0.886396 -0.459603 1.024723 country
m00236.wav -1.911773 -0.548167 -1.736999 0.198717 classical
m00248.wav 1.012103 -1.205427 1.629888 -0.843874 metal
m00253.wav 1.576968 -1.160275 1.795291 -0.381100 blues
m00298.wav -0.155084 -0.290897 -0.230966 -0.845679 blues
m00313.wav -0.936220 1.254430 -1.034527 1.018588 blues
m00338.wav 0.044650 -0.981780 0.763326 -0.323221 blues
m00339.wav 0.482125 -1.738267 0.525650 -1.817315 rock
m00351.wav 0.224508 -1.263761 0.097705 -1.010856 jazz
m00400.wav 0.413194 1.376691 -0.125939 1.418211 blues
m00421.wav -0.872056 1.928538 -0.801542 1.730750 pop
m00429.wav 1.276993 -0.779455 1.302700 -0.201144 hiphop
m00435.wav 0.070265 -0.406651 -1.356252 -0.684829 classical
m00454.wav 1.651569 -0.860163 0.866572 -0.001118 hiphop
m00477.wav -1.759062 1.014908 -1.072470 1.218380 classical
m00501.wav -0.931346 0.920434 -0.964391 0.711141 jazz
m00503.wav 1.292365 -0.752371 1.663279 -0.991667 metal
m00513.wav -0.520090 2.023155 -1.073308 1.984908 pop
m00553.wav -0.259077 0.949580 -0.525501 1.473174 disco
m00606.wav -1.409103 0.318720 -1.283452 0.108399 country
m00623.wav -1.656764 0.225550 -0.769049 0.112965 reggae
m00627.wav 1.234541 -0.580616 0.712332 -1.005324 metal
m00629.wav 0.636595 -1.179701 0.734209 -1.312755 country
m00633.wav 0.452726 -0.776468 0.256885 -0.835520 country
m00637.wav 1.139184 -0.676071 0.930189 0.200930 hiphop
m00658.wav 1.303571 -0.934012 1.555147 -0.171742 hiphop
m00671.wav -0.597876 1.623375 -0.594650 0.952940 reggae
m00676.wav -1.380193 0.640297 -0.997807 0.577001 pop
m00677.wav 0.204576 -0.210049 -0.148832 -1.572489 country
m00678.wav 0.980320 0.007298 0.913661 -0.379877 metal
m00716.wav 0.971441 -0.918776 0.418948 -0.748811 jazz
m00762.wav 0.197099 1.532570 -0.290346 1.487263 disco
m00772.wav 0.645363 -0.608964 0.917988 -1.358662 rock
m00773.wav -0.891454 -0.316985 -0.201543 -0.652296 pop
m00801.wav -0.619027 1.042483 -0.727886 0.569917 rock
m00821.wav 0.829343 -1.145217 1.029918 -1.187887 metal
m00850.wav 0.188095 1.320281 -0.975654 0.907961 disco
m00867.wav 0.747490 -1.185073 0.914528 -1.807509 rock
m00895.wav -0.378459 0.749937 -0.676747 0.842742 rock
m00996.wav -1.176744 0.574570 -0.955205 1.516457 jazz
PCA_Component_1 PCA_Component_2 PCA_Component_3
filename
m00002.wav -0.661286 -3.040738 -3.391739
m00039.wav 1.359245 -1.304717 -0.430524
m00041.wav -3.450563 3.925791 0.799791
m00072.wav -2.770486 1.837407 0.168114
m00096.wav 3.065512 0.807930 1.513794
m00102.wav -2.982837 -5.015197 0.836015
m00112.wav -3.768145 1.397870 -3.189199
m00138.wav 1.565384 -0.615252 -1.399014
m00192.wav -1.767320 -0.841912 2.762178
m00206.wav 3.358468 1.573820 -1.208237
m00230.wav -2.362658 4.596334 1.185582
m00236.wav -2.810388 -4.463865 -1.475698
m00248.wav 4.470725 1.582211 -0.954290
m00253.wav 4.767450 1.480511 1.290450
m00298.wav -1.134283 -1.986452 1.111878
m00313.wav -3.453501 -2.719324 -0.554035
m00338.wav 3.312428 -0.656204 -0.295714
m00339.wav 5.259427 0.172304 3.099247
m00351.wav 1.525518 -1.934687 0.222512
m00400.wav -4.155648 -5.060703 0.082468
m00421.wav -3.648766 5.153067 -0.633728
m00429.wav 3.110103 0.484882 -1.841544
m00435.wav -0.501535 -3.263213 1.712099
m00454.wav 3.092246 1.035876 -2.918171
m00477.wav -3.621328 -4.559819 -1.722098
m00501.wav -1.769586 -0.834441 1.623537
m00503.wav 4.765493 0.303730 -0.310403
m00513.wav -3.595616 6.020670 -2.136228
m00553.wav -2.504267 0.584663 0.746404
m00606.wav -3.033943 -2.268128 1.297916
m00623.wav -2.982673 2.957186 2.197331
m00627.wav 3.480421 0.665182 0.707633
m00629.wav 3.505332 -0.797418 -0.518927
m00633.wav 2.682393 -1.054375 0.450360
m00637.wav 1.959856 0.237172 -2.565966
m00658.wav 3.025122 1.473455 -1.712716
m00671.wav -2.641565 -0.235871 -2.058647
m00676.wav -2.429120 2.636766 1.632049
m00677.wav 0.558315 -3.583280 1.341483
m00678.wav 2.223983 0.260151 -1.337563
m00716.wav 1.520892 -1.279420 -1.250196
m00762.wav -3.541632 3.016091 -1.702286
m00772.wav 4.320138 -1.251159 2.095050
m00773.wav -3.113622 -0.409345 3.494737
m00801.wav -3.639496 2.947230 2.224247
m00821.wav 5.771560 2.146561 0.814633
m00850.wav -1.917320 1.899693 -0.852695
m00867.wav 3.686435 -1.322420 0.842992
m00895.wav -1.567539 1.639482 1.428211
m00996.wav -2.561328 -2.338098 -1.221093
[50 rows x 34 columns]
label_group = label.groupby('genre')[['PCA_Component_1', 'PCA_Component_2', 'PCA_Component_3']].agg('mean')
scdfl = label_group
Met de functie die eerder is gemaakt gaan we kijken naar de afstanden
# Kijken welke clusters tot welke genre horen
bepaal_genres(df_grouped, euclidische_afstand)
bepaal_genres(df_grouped, manhattan_afstand)
bepaal_genres(df_grouped, minkowski_afstand, 2)
Genres volgens euclidische_afstand:
Cluster 0: {'Genre 1': 'reggae', 'Aantal 1': 1, 'Genre 2': 'pop', 'Aantal 2': 1}
Cluster 1: {'Genre 1': 'metal', 'Aantal 1': 2, 'Genre 2': 'country', 'Aantal 2': 1}
Cluster 2: {'Genre 1': 'classical', 'Aantal 1': 2, 'Genre 2': 'blues', 'Aantal 2': 1}
Genres volgens manhattan_afstand:
Cluster 0: {'Genre 1': 'reggae', 'Aantal 1': 1, 'Genre 2': 'pop', 'Aantal 2': 1}
Cluster 1: {'Genre 1': 'metal', 'Aantal 1': 2, 'Genre 2': 'country', 'Aantal 2': 1}
Cluster 2: {'Genre 1': 'classical', 'Aantal 1': 2, 'Genre 2': 'blues', 'Aantal 2': 1}
Genres volgens minkowski_afstand:
Cluster 0: {'Genre 1': 'reggae', 'Aantal 1': 1, 'Genre 2': 'pop', 'Aantal 2': 1}
Cluster 1: {'Genre 1': 'metal', 'Aantal 1': 2, 'Genre 2': 'country', 'Aantal 2': 1}
Cluster 2: {'Genre 1': 'classical', 'Aantal 1': 2, 'Genre 2': 'blues', 'Aantal 2': 1}
{'PCA_Component_1': genre blues classical country disco hiphop jazz metal \
cluster
0 0.748973 1.454998 1.151571 0.651955 3.790843 0.492526 5.02412
1 5.02322 7.227191 4.620622 6.424148 1.98135 5.279667 0.748073
2 2.223338 0.019367 2.625936 0.82241 5.265208 1.966891 6.498485
genre pop reggae rock
cluster
0 2.365853 0.411867 2.493477
1 8.138046 6.18406 3.278716
2 0.891489 1.062498 3.967842 ,
'PCA_Component_2': genre blues classical country disco hiphop jazz metal \
cluster
0 5.263531 7.103897 4.09647 1.845939 2.514055 5.360573 2.483529
1 0.605861 2.446227 0.5612 2.811731 2.143615 0.702903 2.174141
2 0.987384 0.852982 2.154446 4.404976 3.73686 0.890342 3.767386
genre pop reggae rock
cluster
0 0.009706 3.035253 3.038009
1 4.647964 1.622417 1.619661
2 6.241209 3.215662 3.212906 ,
'PCA_Component_3': genre blues classical country disco hiphop jazz metal \
cluster
0 0.49136 0.58685 0.915634 0.139017 1.884976 0.639045 0.051647
1 0.524217 0.619707 0.948491 0.171874 1.852119 0.606188 0.01879
2 0.026973 0.122463 0.451246 0.325371 2.349363 1.103432 0.516035
genre pop reggae rock
cluster
0 0.795675 0.81166 2.1023
1 0.828532 0.778803 2.135157
2 0.331287 1.276047 1.637913 }
Uit deze uitkomst kunnen we de genres niet echt bepalen. We gaan het nu op basis van trial en error proberen te achterhalen. In het hoofdstuk hierboven zijn we er achter gekomen dat de genres bestaan uit classical, metal en pop. We weten voor de PCA data nog niet welke kluster hoort bij welk genre, daarom gaan we het op verschillende manieren proberen en uiteindelijk kiezen we voor degene met de beste kaggle score.
# Maken van dictionary voor vervangen cluster nummers
cluster_map = {
0 : 'classical',
1 : 'metal',
2 : 'pop'
}
# Aanmaken csv voor Kaggle
# kaggle_upload(df, cluster_map, 'submission_cl_me_po_PCA_012')
Deze heeft een accuracy score van 0.20754 op Kaggle dus deze is niet goed. Nu gaan we de volgende proberen.
# Maken van dictionary voor vervangen cluster nummers
cluster_map = {
2 : 'classical',
1 : 'metal',
0 : 'pop'
}
# Aanmaken csv voor Kaggle
# kaggle_upload(df, cluster_map, 'submission_cl_me_po_PCA_210')
Deze heeft een accuracy score van 0.98113 dus dit is de goede volgorde van de genres.
NMF en PCA zijn beide modellen voor dimensioniteitsvermindering. Ze verminderen beide het aantal kenmerken in een dataset, maar hanteren daarbij verschillende methoden. NMF legt bijvoorbeeld de beperking op dat zowel de basis- als de coëfficiëntenmatrices niet-negatieve waarden moeten hebben, wat betekent dat het bijzonder geschikt is voor gegevens waarin negatieve waarden geen nauwkeurige representatie mogelijk maken, zoals bij afbeeldingen en tekstgegevens. Aan de andere kant accepteert PCA zowel positieve als negatieve waarden.
Een ander onderscheidend kenmerk is dat NMF streeft naar het vinden van additieve, niet-negatieve combinaties van patronen, wat leidt tot onderdeelgebaseerde representaties. Dit is in tegenstelling tot PCA, dat orthogonale assen zoekt (hoofdcomponenten) die de variantie maximaliseren, wat resulteert in ongecorreleerde representaties.
Een ander verschil is dat PCA vaak lastiger te interpreteren is dan NMF, omdat de componenten die uit PCA voortkomen niet direct corresponderen met de kenmerken in de dataset, maar eerder lineaire combinaties zijn van deze kenmerken.
(Benameur, 2023)
NMF is een afkorting voor "Non-Negative Matrix Factorisation". NMF is net zoals PCA een manier om dimensionaliteit te reduceren. NMF verschilt van PCA doordat het over het algemeen makkelijker te begrijpen is. Dit komt doordat, zoals de naam zegt, NMF geen negatieve waardes kan bevatten. Dit kan ook een nadeel zijn, omdat het betekent dat NMF niet toe te passen is op datasets die waardes minder dan 0 bevatten. Net zoals PCA principal components heeft, heeft NMF ook componenten.
NMF werkt door het vermenigvuldigen twee matrices, een matrix met de features, en een matrix met de "weights" van elke feature. NMF kan alleen een inschatting maken van de resulterende matrix omdat het probleem niet precies op te lossen is.
Wiskundige uitleg: \begin{align}\begin{aligned}L(W, H) &= 0.5 * ||X - WH||_{loss}^2\\&+ alpha\_W * l1\_ratio * n\_features * ||vec(W)||_1\\&+ alpha\_H * l1\_ratio * n\_samples * ||vec(H)||_1\\&+ 0.5 * alpha\_W * (1 - l1\_ratio) * n\_features * ||W||_{Fro}^2\\&+ 0.5 * alpha\_H * (1 - l1\_ratio) * n\_samples * ||H||_{Fro}^2\end{aligned}\end{align}
$$0.5 \cdot ||X - WH||_{\text{loss}}^2$$
In deze term word getest hoe goed het product van matrices W en H de originele data representeert. "loss" representeert een loss functie tussen de originele matrix X en de gereconstrueerde matrix WH.
$$\alpha_W \cdot \text{l1\_ratio} \cdot \text{n\_features} \cdot ||\text{vec}(W)||_1$$
$$0.5 \cdot \alpha_W \cdot (1 - \text{l1\_ratio}) \cdot \text{n\_features} \cdot ||W||_{\text{Fro}}^2$$
# Omdat nmf niet gebruikt kan worden met negatieve waardes moeten we eerst zorgen
# dat we alleen maar positieve data hebben.
# nmfdf = scdf.copy()
# nmfdf[nmfdf < 0] = 0
# Fill NaN values with zero
# nmfdf = nmfdf.fillna(0)
# nmfdf.head()
# Hier word bepaald hoeveel componenten we gebruiken voor NMF
# components_range = range(1, 10)
# residual_sums_of_squares = []
# Loop om range van componenten te checken
# for n_components in components_range:
# nmf_model = NMF(n_components=n_components)
# nmf_result = nmf_model.fit_transform(nmfdf)
# reconstructed_data = np.dot(nmf_result, nmf_model.components_)
# rss = np.sum((nmfdf - reconstructed_data) ** 2)
# residual_sums_of_squares.append(rss)
# Plot om het optimate aantal componenten te bepalen
# plt.plot(components_range, residual_sums_of_squares, marker='o')
# plt.xlabel('Aantal componenten')
# plt.ylabel('Residual Sum of Squares')
# plt.title('Elleboog methode')
# plt.show()
# Omdat het dataframe op dit punt nog veranderd, hebben er voor gekozen om een functie aan te maken voor het toepassen van NMF.
def apply_nmf(dataframe, n_components=4, random_state=42):
"""
Non-Negative Matrix Factorisation toepassen op een dataframe.
Parameters:
- dataframe: DataFrame zonder negatieve waardes.
- n_components (int): aantal componenten.
- random_state (int): Seed voor random state.
Returns:
- nmf_result (DataFrame): gefactoriseerde output van het originele DataFrame.
"""
# NMF initialseren
nmf_model = NMF(n_components=n_components, random_state=random_state)
# NMF toepassen
nmf_result = pd.DataFrame(nmf_model.fit_transform(dataframe), index=dataframe.index)
return nmf_result
# best_nmf = apply_nmf(data, n_components=4, random_state=42)
NMF staat voor Non-negative Matrix Factorisation. Hierbij is er een erg belangrijk gedeelte Non-negative, aangezien de huidige scaled dataframe met de features negatieve waarden bevat. Om dit te voorkomen zullen we de data van NMF gaan scalen met behulp van de MinMaxScaler.
# Aanmaken van de scaler
minmax = MinMaxScaler()
# Toepassen van de scaler op unlabeled (behalve eerste kolommen)
scaled_data = minmax.fit_transform(unlabeled.iloc[:, 2:])
# Omzetten van de data naar een dataframe
scaled_df = pd.DataFrame(scaled_data,
columns=unlabeled.columns[2:],
index=unlabeled.index)
# Tonen van describe om te kijken of het heeft gewerkt
display(scaled_df.describe())
| mfcc1_mean | mfcc2_mean | mfcc3_mean | mfcc4_mean | mfcc5_mean | mfcc6_mean | mfcc7_mean | mfcc8_mean | mfcc9_mean | mfcc10_mean | ... | mean_bandwidth | mean_centroids | mean_spectral_contrast | mean_tonnetz | mean_spectral_rolloff | mean_rms_energy | mean_zcr | mean_beat | mean_tempo | mean_harmonie | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 105.000000 | 105.000000 | 105.000000 | 105.000000 | 105.000000 | 105.000000 | 105.000000 | 105.000000 | 105.000000 | 105.000000 | ... | 105.000000 | 105.000000 | 105.000000 | 105.000000 | 105.000000 | 105.000000 | 105.000000 | 105.000000 | 105.000000 | 105.000000 |
| mean | 0.713325 | 0.439719 | 0.399065 | 0.428874 | 0.559996 | 0.463504 | 0.480816 | 0.525584 | 0.450691 | 0.487541 | ... | 0.525956 | 0.495121 | 0.411253 | 0.458024 | 0.502898 | 0.415691 | 0.479663 | 0.332460 | 0.382754 | 0.926527 |
| std | 0.242423 | 0.268921 | 0.240074 | 0.249618 | 0.214397 | 0.225499 | 0.204837 | 0.229628 | 0.246470 | 0.232694 | ... | 0.275154 | 0.268842 | 0.242931 | 0.197144 | 0.275531 | 0.270905 | 0.231315 | 0.083735 | 0.204204 | 0.123128 |
| min | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
| 25% | 0.499268 | 0.227978 | 0.211305 | 0.242759 | 0.403624 | 0.322576 | 0.318539 | 0.395461 | 0.264517 | 0.349721 | ... | 0.252340 | 0.210687 | 0.208707 | 0.336822 | 0.217757 | 0.127072 | 0.275954 | 0.317687 | 0.240000 | 0.956587 |
| 50% | 0.820982 | 0.378098 | 0.354820 | 0.371553 | 0.573954 | 0.415137 | 0.451669 | 0.467025 | 0.404343 | 0.441233 | ... | 0.517625 | 0.542856 | 0.418055 | 0.441245 | 0.540074 | 0.425861 | 0.485893 | 0.333108 | 0.335664 | 0.961914 |
| 75% | 0.896399 | 0.692374 | 0.617295 | 0.620374 | 0.725300 | 0.552778 | 0.644463 | 0.707698 | 0.649023 | 0.628641 | ... | 0.805383 | 0.691017 | 0.584579 | 0.558136 | 0.750373 | 0.649673 | 0.648706 | 0.347204 | 0.512821 | 0.962877 |
| max | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | ... | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 |
8 rows × 30 columns
Nu het dataframe is aangemaakt kan de het optimale aantal componenten voor NMF worden bepaald. Dit gebeurt ook door gebruik van de elleboog methode.
# Aanmaken lijst voor waarden en range voor componenten
errors = []
componenten = range(1, 11)
# Loopen over alle aantallen componenten
for n_components in componenten:
# Toepassen NMF
nmf = NMF(n_components=n_components, random_state=42)
# Fit_Transform de data
nmf.fit_transform(scaled_df)
# Opslaan errors
errors.append(nmf.reconstruction_err_)
# Plotten van de verschillende errors
plt.plot(componenten, errors, '-x')
plt.xlabel('Aantal componenten')
plt.ylabel('Reconstruction Error')
plt.show()
Het punt waar de lijn het meest op een elleboog lijkt is bij een aantal componenten van 3. Dit is de waarde waarmee we NMF gaan trainen.
# Toepassen van NMF
nmf = NMF(n_components=3, random_state=42)
# Fit en transformeer de data
nmf_result = nmf.fit_transform(scaled_df)
# Aanmaken van dataframe met de componenten
nmf_df = pd.DataFrame(nmf_result,
columns=[f'NMF_Component_{i+1}' for i in range(3)],
index=unlabeled.index)
# Tonen van het dataframe
display(nmf_df)
| NMF_Component_1 | NMF_Component_2 | NMF_Component_3 | |
|---|---|---|---|
| filename | |||
| m00003.wav | 0.022219 | 0.538570 | 0.331300 |
| m00012.wav | 0.071243 | 0.658065 | 0.157665 |
| m00013.wav | 0.000000 | 0.395245 | 0.689557 |
| m00043.wav | 0.036629 | 0.530188 | 0.291035 |
| m00044.wav | 0.033812 | 0.000000 | 0.631686 |
| ... | ... | ... | ... |
| m00971.wav | 0.084712 | 0.062685 | 0.564332 |
| m00973.wav | 0.383193 | 0.198367 | 0.008603 |
| m00988.wav | 0.383194 | 0.186904 | 0.000000 |
| m00991.wav | 0.003947 | 0.174468 | 0.862957 |
| m00995.wav | 0.000000 | 0.205393 | 0.719270 |
105 rows × 3 columns
Nu we dit dataframe hebben gaan we opnieuw kijken naar de elleboog van KMeans, om te kijken of het aantal clusters is veranderd na het toepassen van NMF.
# Toepassen elleboog methode
elleboog_KMeans(11, nmf_df)
Nu het optimale aantal clusters is bepaald, namelijk k=3, kan er weer geclustered worden.
# Toepassen van KMeans_Clustering
nmf_df = clusteren(3, nmf_df)
1 40 0 39 2 26 Name: cluster, dtype: int64
Na het clusteren zien we weer exact dezelfde verdeling als de vorige twee keren, namelijk 40, 39, 26. We zullen nu eerst gaan kijken hoe de clusters zich tonen in een scatterplot.
# Tonen van de clusters op een 2D-schaal
sns.scatterplot(data=nmf_df,
x='NMF_Component_2',
y='NMF_Component_3',
hue='cluster',
palette='viridis')
plt.title('NMF cluster verdeling')
plt.show()
Nu er een mooi 2 dimensionale visualisatie is gemaakt van de clusters, kijken we naar een 3 dimensionale visualisatie.
# Bepalen figuur grootte en assen
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')
# Aanmaken scatterplot met 3 assen
scatter = ax.scatter(nmf_df['NMF_Component_1'],
nmf_df['NMF_Component_2'],
nmf_df['NMF_Component_3'],
c=nmf_df['cluster'],
cmap='viridis')
# Aanmaken as-titels en titel
ax.set_xlabel('NMF_Component_1')
ax.set_ylabel('NMF_Component_2')
ax.set_zlabel('NMF_Component_3')
ax.set_title('3D Scatteplot met NMF Clusters')
fig.colorbar(scatter, label='Cluster')
plt.show()
Zoals we kunnen zien zijn er drie mooie clusters aangemaakt door NMF. Nu dit is gedaan kijken we naar de genres die toebehoren aan elk cluster. We weten dat we keuze hebben uit 'metal', 'pop' en 'classical'. Om dit te bereiken passen we NMF toe op de gelabelde dataset, vervolgens visualiseren we de drie eerder genoemde genres in een 2 dimensionaal scatterplot, samen met de NMF clusters.
# Aanmaken van de scaler
minmax = MinMaxScaler()
# Toepassen van de scaler op unlabeled (behalve eerste kolommen)
scaled_data_label = minmax.fit_transform(labeled.iloc[:, 3:])
# Omzetten van de data naar een dataframe
scaled_df_label = pd.DataFrame(scaled_data_label,
columns=labeled.columns[3:],
index=labeled.index)
# Tonen van describe om te kijken of het heeft gewerkt
display(scaled_df_label.describe())
| mfcc1_mean | mfcc2_mean | mfcc3_mean | mfcc4_mean | mfcc5_mean | mfcc6_mean | mfcc7_mean | mfcc8_mean | mfcc9_mean | mfcc10_mean | ... | mean_bandwidth | mean_centroids | mean_spectral_contrast | mean_tonnetz | mean_spectral_rolloff | mean_rms_energy | mean_zcr | mean_beat | mean_tempo | mean_harmonie | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 50.000000 | 50.000000 | 50.000000 | 50.000000 | 50.000000 | 50.000000 | 50.000000 | 50.000000 | 50.000000 | 50.000000 | ... | 50.000000 | 50.000000 | 50.000000 | 50.000000 | 50.000000 | 50.000000 | 50.000000 | 50.000000 | 50.000000 | 50.000000 |
| mean | 0.647249 | 0.487332 | 0.419637 | 0.500381 | 0.478063 | 0.523164 | 0.455409 | 0.491749 | 0.477961 | 0.499348 | ... | 0.505844 | 0.470589 | 0.453101 | 0.386478 | 0.486691 | 0.405863 | 0.449987 | 0.825163 | 0.461538 | 0.472540 |
| std | 0.227837 | 0.204479 | 0.213392 | 0.251376 | 0.234638 | 0.276432 | 0.264650 | 0.285977 | 0.265674 | 0.281112 | ... | 0.207672 | 0.201221 | 0.223718 | 0.188257 | 0.202795 | 0.218951 | 0.214010 | 0.141550 | 0.178700 | 0.152046 |
| min | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | ... | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
| 25% | 0.520977 | 0.354092 | 0.282704 | 0.306407 | 0.327383 | 0.280541 | 0.247423 | 0.232204 | 0.263917 | 0.223888 | ... | 0.399856 | 0.358767 | 0.279424 | 0.290832 | 0.395530 | 0.275181 | 0.288065 | 0.815907 | 0.354667 | 0.506262 |
| 50% | 0.718615 | 0.504105 | 0.373851 | 0.512183 | 0.439452 | 0.572648 | 0.375780 | 0.452855 | 0.435074 | 0.473759 | ... | 0.466852 | 0.443709 | 0.464893 | 0.373760 | 0.482860 | 0.377013 | 0.449679 | 0.850887 | 0.466667 | 0.521117 |
| 75% | 0.800279 | 0.603771 | 0.552962 | 0.699577 | 0.654092 | 0.744517 | 0.678699 | 0.729770 | 0.712470 | 0.718190 | ... | 0.604076 | 0.588606 | 0.613417 | 0.464425 | 0.599470 | 0.510883 | 0.569762 | 0.867246 | 0.560000 | 0.522372 |
| max | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | ... | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 | 1.000000 |
8 rows × 30 columns
Nu gaan we door naar NMF. Omdat de instantie van NMF nog steeds bestaat hoeft deze niet opnieuw te worden aangemaakt. Na het toepassen van NMF kunnen de genres weer aan de data worden toegevoegd. Daarna kan de data op basis van genre worden verkleind, zodat alleen de nodige data overblijft.
# Fit en transformeer de data
nmf_label = nmf.fit_transform(scaled_df_label)
# Aanmaken van dataframe met de componenten
nmf_df_label = pd.DataFrame(nmf_label,
columns=[f'NMF_Component_{i+1}' for i in range(3)],
index=labeled.index)
# Toevoegen van de genres aan de data
nmf_df_label['genre'] = labeled['genre']
# Fileteren op de data voor de juiste genres
nmf_label_filtered = nmf_df_label[
nmf_df_label['genre'].isin(['pop', 'classical', 'metal'])
]
# Tonen van het dataframe
display(nmf_label_filtered.head())
| NMF_Component_1 | NMF_Component_2 | NMF_Component_3 | genre | |
|---|---|---|---|---|
| filename | ||||
| m00041.wav | 0.007348 | 0.144511 | 1.019306 | pop |
| m00102.wav | 0.445012 | 0.000000 | 0.074475 | classical |
| m00192.wav | 0.140916 | 0.155811 | 0.432768 | classical |
| m00236.wav | 0.484691 | 0.029504 | 0.108606 | classical |
| m00248.wav | 0.055577 | 0.754090 | 0.166697 | metal |
Nu de data voor beide datasets in orde is kan er gebruik worden gemaakt van de scatterplots.
vergelijking_genres(
data1=nmf_df,
data2=nmf_label_filtered,
x='NMF_Component_2',
y='NMF_Component_3'
)
Uit deze grafiek wordt al snel duidelijk bij welk cluster welke genre toebehoord. Met deze informatie kunnen we een bestand aanmaken om op Kaggle in te leveren.
# Maken van de cluster map
cluster_map = {
0 : 'classical',
1 : 'pop',
2 : 'metal'
}
# Kaggle CSV aanmaken
# kaggle_upload(nmf_df, cluster_map, 'submission_cl_po_me_NMF')
Raar genoeg gaf deze order een score van 0.2. Om toch nog even te kijken naar de verdeling gaan we het 3 dimensionale plot maken van de gelabelde dataset, en deze vergelijken met de dataset van de NMF clusters. Om dit te doen veranderen we eerst de genres naar nummers, aangezien een 3d plot geen genres als kleur indicator aanneemt.
# Aanmaken genre map
genre_map = {
'classical' : 0,
'pop' : 1,
'metal' : 2
}
# Copy maken van dataset
nmf_label_visu = nmf_label_filtered.copy()
# Aanpassen van genres naar getallen
nmf_label_visu['genre'] = nmf_label_filtered['genre'].replace(genre_map)
# Bepalen figuur grootte en assen
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')
# Aanmaken scatterplot met 3 assen
scatter = ax.scatter(nmf_label_visu['NMF_Component_1'],
nmf_label_visu['NMF_Component_2'],
nmf_label_visu['NMF_Component_3'],
c=nmf_label_visu['genre'],
cmap='viridis')
# Aanmaken as-titels en titel
ax.set_xlabel('NMF_Component_1')
ax.set_ylabel('NMF_Component_2')
ax.set_zlabel('NMF_Component_3')
ax.set_title('3D Scatteplot met NMF Genres')
fig.colorbar(scatter, label='Genres')
plt.show()
Ook op de 3 dimensionale schaal liggen de NMF clusters op dezelfde plek als de eerder aangegeven genres. Om deze reden gaan we een klein beetje trial and error toepassen. We draaien als eerste classical en pop om.
# Maken van de cluster map
cluster_map = {
0 : 'pop',
1 : 'classical',
2 : 'metal'
}
# Kaggle CSV aanmaken
# kaggle_upload(nmf_df, cluster_map, 'submission_po_cl_me_NMF')
Deze volgorde gaf, zoals iets meer verwacht, de juiste uitkomst van 0.98113. Wat de reden hierachter is, is voor ons niet duidelijk.
Voordat we verder kunnen zullen eerst de cluster omzetten naar genres. Dit zorgt voor een leesbaarder display van de app.
# Maken van dictionary voor vervangen cluster nummers
cluster_map = {
0 : 'classical',
1 : 'pop',
2 : 'metal'
}
# Vervangen van clusternummers door genres
scdf['cluster'] = scdf['cluster'].replace(cluster_map)
Een andere opgave die we hadden was om een eenvoudige app te maken voor het aanbevelen van muziek. Om dit te doen is er gekozen om muziek aan te bevelen op basis van het genre. Om hiermee te beginnen moet het lukken om op basis van het genre een audio bestand af te spelen en bestanden aan te raden. Om dit te doen is de onderstaande code ontwikkelt.
def speel_lied(genre):
"""
Speelt een willekeurig nummer van het opgegeven muziekgenre af
en geeft suggesties voor andere nummers binnen hetzelfde genre.
Parameters:
----------
genre : str
Het gewenste muziekgenre.
Returns:
----------
suggesties : list
Een lijst met suggesties voor andere nummers binnen hetzelfde genre.
Als het opgegeven genre niet wordt gevonden, wordt None geretourneerd.
"""
# Selecteer een willekeurig genre
genre_songs = labeled[labeled['genre'] == genre]
# Indien het genre niet is gevonden, geef bericht
if len(genre_songs) == 0:
print(f"Geen muziek gevonden voor: {genre}."
f"Probeer een van deze: {labeled['genre'].unique()}")
return None
# Pak een willekeurig nummer
random_song = genre_songs.sample(1).index[0]
# Display and play the selected song
audio = os.path.join('labeled/', random_song)
display(Audio(filename=audio))
# Return suggestions for the selected genre
suggesties = genre_songs.sample(5)
return suggesties.index.tolist()
# Selecteren genre en gebruiken functie
selected_genre = 'pop'
suggesties = speel_lied(selected_genre)
# Printen van suggesties
print(f"\nSuggesties voor {selected_genre} genre:")
print(suggesties)
Suggesties voor pop genre: ['m00513.wav', 'm00041.wav', 'm00773.wav', 'm00421.wav', 'm00676.wav']
Nu dit is gelukt, gaan we ons leven wat lastiger maken. Om te zorgen dat er een handmatig gewisseld kan worden tussen genres en nummers zijn hiervoor knoppen aangemaakt met behulp van de ipywidgets libary.
def speel_r_lied(genre):
"""
Speelt een willekeurig nummer van het opgegeven muziekgenre af
en geeft suggesties voor andere nummers binnen hetzelfde genre.
Parameters:
----------
genre : str
Het gewenste muziekgenre.
Returns:
----------
None
"""
# Selecteer een willekeurig genre
genre_songs = labeled[labeled['genre'] == genre]
# Indien het genre niet is gevonden, geef bericht
if len(genre_songs) == 0:
print(f"Geen muziek gevonden voor: {genre}."
f"Probeer een van deze: {labeled['genre'].unique()}")
return None
# Pak een willekeurig nummer
random_song = genre_songs.sample(1).index[0]
# Toon bestandsnaam en genre
with output_area:
clear_output(wait=True)
display(HTML(f"<h1>--Genre: {genre}--</h1><h3>{random_song}</h3>"))
# Tonen en speelbaar maken lied
audio = os.path.join('labeled/', random_song)
display(Audio(filename=audio))
# Plaats suggesties voor het genre
suggesties = genre_songs.sample(5)
with output_area:
print(f"\nSuggesties voor {genre} genre:")
suggesties_knop = [widgets.Button(description=song, layout=widgets.Layout(width='100%')) for song in suggesties.index]
for button in suggesties_knop:
button.on_click(suggestie_knop)
display(*suggesties_knop)
def suggestie_knop(button):
"""
Zorgt voor de suggestie knoppen
Parameters:
----------
button : widgets.Button
De button voor de suggestie
Returns:
----------
None
"""
with output_area:
speel_g_lied(button.description)
def speel_g_lied(song):
"""
Afspelen van geselecteerd lied
Parameters:
----------
song : str
het lied
Returns:
----------
None
"""
# Tonen bestandnaam en genre
with output_area:
clear_output(wait=True)
display(HTML(f"<h1>--Genre: {genre}--</h1><h3>{song}</h3>"))
# Tonen en afspeelbaar maken lied
audio = os.path.join('labeled/', song)
display(Audio(filename=audio))
# Verversen van suggesties
suggesties = labeled[labeled['genre'] == genre].sample(5)
with output_area:
print(f"\nSuggesties voor {genre} genre:")
suggesties_knop = [widgets.Button(description=song, layout=widgets.Layout(width='100%')) for song in suggesties.index]
for button in suggesties_knop:
button.on_click(suggestie_knop)
display(*suggesties_knop)
def genre_knop(button):
"""
Zorgt voor het handelen van de genre knoppen
Parameters:
----------
button : widgets.Button
De button voor het genre
Returns:
----------
None
"""
with output_area:
global genre
genre = button.description
speel_r_lied(genre)
# Ophalen van genres
genres = labeled['genre'].unique()
# Knop maken voor elk genre
genre_knoppen = [widgets.Button(description=genre) for genre in genres]
# Attach the callback function to button clicks
for button in genre_knoppen:
button.on_click(genre_knop)
# Maak output area voor lied en suggesties
output_area = widgets.Output()
# Random button
random_button = widgets.Button(description="Random Pick")
random_button.on_click(lambda _: speel_r_lied(random.choice(genres)))
# Tonen resultaat
buttons_box = widgets.HBox(genre_knoppen)
display(buttons_box, output_area, random_button)
HBox(children=(Button(description='jazz', style=ButtonStyle()), Button(description='reggae', style=ButtonStyle…
Output()
Button(description='Random Pick', style=ButtonStyle())
Nu dit is gemaakt kunnen we deze praktijken verder verbeteren en een volledige class maken die functioneert als een app. Deze app heeft als input een dataset, een genre kolom en een directory nodig om te kunnen werken.
class TuneTips:
"""
Een class die een app opstart. Deze app kan muziekfragmenten
afspelen en muziek aanraden op basis van genres.
"""
def __init__(self, root, dataframe, genre_column, directory):
"""
Initialisatie van de MusicPlayerApp.
Parameters:
----------
root : Tk
Het hoofdvenster van de applicatie.
dataframe : pandas.DataFrame
Het DataFrame met informatie over de nummers.
genre_column : str
De kolom in het DataFrame die het genre van de nummers bevat.
directory : str
Het pad naar de map met de muziekbestanden.
"""
# Aanmaken venster
self.root = root
self.root.title("Tune Tips - Music Advisor (BETA-EDITION)")
# Initialiseren van mixer
mixer.init()
# Gebruiken van doorgegeven df, kolom en dir
self.labeled = dataframe
self.genre_column = genre_column
self.directory = directory
# Ophalen genres in de genre kolom
self.available_genres = self.labeled[self.genre_column].unique()
# Aanmaken van genre knoppen
self.genre_buttons = [ttk.Button(root,
text=genre,
command=lambda g=genre: self.play_random_song(g)
) for genre in self.available_genres]
# Aanmaken genre label
self.genre_label = ttk.Label(root, text="Genres:")
self.genre_label.grid(row=7, column=3, columnspan=2, sticky="w")
# Genre knoppen plaatsen
for i, button in enumerate(self.genre_buttons):
button.grid(row=8 + i, column=3, sticky="nsew")
# Maken display window voor tekst
self.d_window = tk.Text(root, height=12, width=100)
self.d_window.grid(row=1, column=3, rowspan=6, columnspan=4)
self.d_window.delete(1.0, tk.END)
self.d_window.tag_configure("big", font=('Helvetica', 42), justify='center')
self.d_window.insert(tk.END, f"Kies een genre of klik op\nRandom Song", "big")
self.d_window.yview(tk.MOVETO, 0.0)
self.d_window.config(state=tk.DISABLED)
# Aanmaken suggesties label
self.suggestions_label = ttk.Label(root, text="Song Suggestions:")
self.suggestions_label.grid(row=7, column=4, columnspan=2, sticky="w")
# Aanmaken en plaatsten suggestie knoppen
self.suggestion_buttons = [ttk.Button(root,
text="",
command=lambda i=i: self.play_selected_song(i),
width=18
) for i in range(10)]
for i, button in enumerate(self.suggestion_buttons):
button.grid(row=8 + i, column=4, sticky="w")
# Volume control
self.volume_label = ttk.Label(root, text="Volume:")
self.volume_scale = ttk.Scale(root, from_=0, to=100,
orient=tk.HORIZONTAL,
command=self.set_volume,
length=400)
self.volume_label.grid(row=9, column=5, sticky="e")
self.volume_scale.grid(row=9, column=6, sticky="w")
self.volume_scale.set(50)
# Play/Pause knop
self.playing = False
self.play_pause_button = ttk.Button(root, text="Play",
command=self.play_pause_music)
self.play_pause_button.grid(row=10, column=5,
rowspan=2, columnspan=1, sticky="ew")
# Restart knop
self.restart_button = ttk.Button(root, text="Restart",
command=self.restart_music)
self.restart_button.grid(row=10, column=6,
rowspan=2, columnspan=1, sticky="ew")
# Random Song knop
self.random_number = ttk.Button(root, text="Random Song",
command=self.random_number)
self.random_number.grid(row=11, column=5,
rowspan=2, columnspan=2, sticky="ew")
# Voortgangs display
self.progress_label = ttk.Label(root, text="Voortgang:")
self.progress_var = tk.DoubleVar()
self.progress_bar = ttk.Progressbar(root, orient=tk.HORIZONTAL,
mode='determinate',
variable=self.progress_var,
length=400)
self.progress_label.grid(row=8, column=5, sticky="e")
self.progress_bar.grid(row=8, column=6, sticky="w")
# Updaten elke 100 ms
self.root.after(100, self.update_progress)
def update_suggestions(self, genre):
"""
Update de suggestieknoppen op
basis van het geselecteerde genre.
Parameters:
----------
genre : str
Het geselecteerde genre.
"""
# Ophalen genre en huidig nummer
genre_songs = self.labeled[self.labeled[self.genre_column] == genre]
random_song = self.current_song
self.current_genre = genre
# Geven van suggesties op basis van gelijkenis
suggestions = self.get_unique_suggestions(genre_songs, random_song, 10)
suggestions_sorted = self.sort_suggestions_by_similarity(suggestions)
for i, song in enumerate(suggestions_sorted):
self.suggestion_buttons[i].config(text=song)
def sort_suggestions_by_similarity(self, suggestions):
"""
Sorteer de suggesties op basis van
cosinusgelijkenis met het huidige nummer.
Parameters:
----------
suggestions : list of str
Een lijst met nummers als suggesties.
Returns:
----------
sorted_suggestions : list of str
Een lijst met nummers gesorteerd op cosinusgelijkenis.
"""
# Bereken de features voor het huidige nummer
current_song_features = self.get_song_features(self.current_song)
# Bereken de cosinusgelijkenis met elk nummer
similarity_scores = []
for song in suggestions:
song_features = self.get_song_features(song)
similarity_score = cosine_similarity(
[current_song_features], [song_features]
)[0][0]
similarity_scores.append(similarity_score)
# Sorteer de suggesties op basis van de cosinusgelijkenis
sorted_suggestions = [song for _, song in sorted(
zip(similarity_scores, suggestions), reverse=True
)]
return sorted_suggestions
def get_song_features(self, song):
"""
Haal de features van het
nummer op uit het DataFrame.
Parameters:
----------
song : str
Het nummer waarvan de
features moeten worden opgehaald.
Returns:
----------
features : numpy array
Een array met de features van het nummer.
"""
# Selecteren van bruikbare features
features = self.labeled.loc[song, self.labeled.columns != 'cluster'].values
return features
def play_random_song(self, genre):
"""
Speel een willekeurig nummer af van het opgegeven genre.
Parameters:
----------
genre : str
Het genre van de nummers om uit te kiezen.
"""
# Indien er muziek speelt, update alleen de buttons
if self.playing:
self.update_suggestions(genre)
return
# Ophalen genres en muziek
genre_songs = self.labeled[self.labeled[self.genre_column] == genre]
random_song = genre_songs.sample(1).index[0]
# Tonen van bestand en genre
self.d_window.config(state=tk.NORMAL)
self.d_window.delete(1.0, tk.END)
self.d_window.tag_configure("big", font=('Helvetica', 42), justify='center')
self.d_window.insert(tk.END, f"Currently playing: {random_song}"
f"\n--Genre: {genre}--", "big")
self.d_window.yview(tk.MOVETO, 0.0)
self.d_window.config(state=tk.DISABLED)
# Opslaan van het genre en nummer
self.current_genre = genre
self.current_song = random_song
# Speel het bestand af
file_path = os.path.join(self.directory, random_song)
mixer.music.load(file_path)
mixer.music.play()
# Geef suggesties op basis van genre
suggestions = self.get_unique_suggestions(genre_songs, random_song, 10)
for i, song in enumerate(suggestions):
self.suggestion_buttons[i].config(text=song)
# Zet voortgangsbar aan
song_length = mixer.Sound(file_path).get_length()
self.progress_bar["maximum"] = int(song_length)
# Update status
self.playing = True
self.play_pause_button.config(text="Pause")
def get_unique_suggestions(self, genre_songs, current_song, num_suggestions):
"""
Genereer unieke suggesties voor het geselecteerde genre.
Parameters:
----------
genre_songs : pandas.DataFrame
DataFrame met nummerinformatie van het geselecteerde genre.
current_song : str
Het nummer dat momenteel wordt afgespeeld.
num_suggestions : int
Het aantal suggesties dat moet worden gegenereerd.
Returns:
----------
suggestions : list of str
Een lijst met unieke suggesties.
"""
# Excludeer huidig nummer
genre_songs = genre_songs[genre_songs.index != current_song]
# Haal suggesties op
suggestions = []
while len(suggestions) < num_suggestions and not genre_songs.empty:
song = genre_songs.sample(1).index[0]
suggestions.append(song)
genre_songs = genre_songs[genre_songs.index != song]
return suggestions
def play_selected_song(self, index):
"""
Speel het geselecteerde nummer af op
basis van de index van de suggestieknop.
Parameters:
----------
index : int
De index van de suggestieknop.
"""
# Haal nummer op, op basis van knop
selected_song = self.suggestion_buttons[index].cget("text")
file_path = os.path.join(self.directory, selected_song)
mixer.music.load(file_path)
mixer.music.play()
# Updaten display tekst
self.d_window.config(state=tk.NORMAL)
self.d_window.delete(1.0, tk.END)
self.d_window.tag_configure("big", font=('Helvetica', 42), justify='center')
self.d_window.insert(tk.END, f"Currently playing: {selected_song}"
f"\n--Genre: {self.current_genre}--", "big")
self.d_window.yview(tk.MOVETO, 0.0)
self.d_window.config(state=tk.DISABLED)
# Opslaan van nummer
self.current_song = selected_song
# Update status
self.playing = True
self.play_pause_button.config(text="Pause")
def random_number(self):
"""
Speel een willekeurig nummer
af van een willekeurig genre.
"""
# Kiezen willekeurig genre
genre = random.choice(self.available_genres)
self.play_random_song(genre)
def restart_music(self):
"""
Herstart het afspelen van het huidige nummer.
"""
# Indien er muziek speelt
if self.playing:
mixer.music.rewind()
mixer.music.play()
# Begin opnieuw wanneer op pauze
# (zonder code wilde dit niet werken)
else:
file_path = os.path.join(self.directory, self.current_song)
mixer.music.load(file_path)
mixer.music.play()
# Updaten status
self.playing = True
self.play_pause_button.config(text="Pause")
def set_volume(self, val):
"""
Stel het volume in op basis van de opgegeven waarde.
Parameters:
----------
val : float
De waarde om het volume in te
stellen (tussen 0 en 100).
"""
# Regelen van volumeknop
volume = float(val) / 100
mixer.music.set_volume(volume)
def play_pause_music(self):
"""
Pauzeer of hervat het afspelen van muziek
"""
# Als muziek speelt, pauzeer
if mixer.music.get_busy():
mixer.music.pause()
self.play_pause_button.config(text="Play")
# Zo niet, speel muziek
else:
mixer.music.unpause()
self.play_pause_button.config(text="Pause")
# Wissel van staat
self.playing = not self.playing
def update_progress(self):
"""
Voortgangsbalk op basis
van de huidige afspeeltijd.
"""
# Indien muziek speelt
if mixer.music.get_busy():
current_time = mixer.music.get_pos() / 1000
self.progress_var.set(current_time)
else:
# Als muziek klaar is, zet knop op tekst Play
self.play_pause_button.config(text="Play")
self.playing = False
# update elke 100 ms
self.root.after(100, self.update_progress)
Nu de class is gemaakt kan deze worden aangeroepen. Zodra dit gebeurt opent er een extra window. Deze window bevat knoppen waarop gedrukt kan worden om handelingen uit te voeren binnen de applicatie. De eerste cel is in comments gezet zodat deze niet zal runnen bij run-all. Dit is voornamelijk omdat we liever hebben dat de app ervaren wordt met de geclusterde data.
# Gebruiken van de app op labeled
# root = tk.Tk()
# app = MusicPlayerApp(root, labeled, 'genre', 'labeled/')
# root.mainloop()
# Gebruiken van de app op unlabeled
root = tk.Tk()
app = TuneTips(root, scdf, 'cluster', 'unlabeled/')
root.mainloop()
Om te bepalen wat de belangrijkste features zijn in het clustering algoritme, wordt een Random Forest model getrained. Door dit model te trainen kunnen we kijken naar welke features het meetste aangeven over het kiezen van genres. Om hieraan te beginnen gaan we de data splitsen in een train en een test dataset.
# Selecteren van target
target = 'cluster'
# Making the SEED
SEED = 42
# Splitting the data into X and y
X = scdf.drop(target, axis=1)
y = scdf[target]
# Applying train_test_split to get train and test data
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=SEED
)
Nu de data is gesplitst in een train en een test set kan het model worden getrained. Dit wordt gedaan met een paar parameters om overfitting te voorkomen.
# Uitvoeren van random forest
rf = RandomForestClassifier(
max_depth=10,
n_estimators=100,
min_samples_leaf=2,
n_jobs=-1,
random_state=SEED
)
# Fitten van de data in het model
rf.fit(X_train, y_train)
# Voorspellen van de uitkomsten
y_pred = rf.predict(X_test)
Nu het model heeft voorspeld is het mogelijk om naar de feature importances te kijken en deze te visualiseren.
# Aanmaken en sorteren invloed kolommen
invloed = pd.Series(rf.feature_importances_, index=X.columns)
invloed = invloed.sort_values(ascending=True)
# Plotten grafiek met invloed
invloed.plot(kind='barh', figsize=(10, 20))
plt.ylabel('Features')
plt.xlabel('Invloed')
plt.show()
In de grafiek is te zien dat volgens het Random Forest model, de gemiddelde bandbreedte het meeste invloed heeft gehad op de bepalen van de genres in de dataset. Dit geeft indirect ook aan dat de bandbreedte het meest doorslaggevend is geweest bij het maken van de clusters, aangezien het supervised learning model hierin de beste patronen kan herkennen. Naast de bandbreedte zijn ook een aantal van de MFCC features, de spectral rolloff en de centroids meer van belang geweest voor het voorspellen.
Aan de andere kant van de grafiek is te zien dat harmonie, tempo en beat eigenlijk weinig indicatie gaven over het genre van de clusters. Tussen deze waarden zitten ook een aantal MFCC waarden, dit geeft duidelijk aan dat niet alle MFCC's even belangrijk zijn of evenveel impact hebben op het voorspellen van de clusters. Met een gecombineerd aantal van 20 MFCC waarden, is het niet apart dat er een aantal met hoge invloed en een aantal met lage invloed aanwezig zijn.
Naast deze standaard features is er ook gebruik gemaakt van dimensionaliteits reductie, om een andere kijk op de features te krijgen. Hoewel we in onze scores op Kaggle geen verschil zien, zijn er wel voordelen van het gebruiken van deze technieken. Het eerste voor is dat het model minder complex wordt, aangezien er minder features aanwezig zijn. Daarnaast zorgt het gebruik van PCA of NMF ook voor een snellere trainings tijd, dit komt door dezelfde redenen. Minder input data geeft een snellere output omdat er minder patronen zijn om over na te denken. Ook helpt het gebruik van PCA of NMF met het verwijderen van eventuele ruis in de data, waardoor er minder kans is op overfitting. Door de ruis te behandelen wordt ook enige overbodige informatie verwijderd. (Simplilearn, 2023)
In onze opdracht zouden we echter kiezen om toch voor de oorspronkelijke data te gaan. Aangezien deze data meteen de clusters op de juiste plekken wist te plaatsen, anders dan bij NMF. Daarnaast zijn er nog wel mogelijkheden om het model te verbeteren. Deze mogelijkheden liggen voornamelijk in het vinden en doorspitten van meer data. Naar onze mening zouden deze vier punten een goede impact kunnen hebben:
Aan de hand van sommige punten zou het niet alleen mogelijk moeten zijn om de voorspellingen meer aan te scherpen, maar het zou ook een mogelijkheid geven om nog verder te kijken. Zo kent een genre zoals metal verschillende subgenres. Deze zouden aan de hand van tekst-analyse, de volledige audiofragmenten en instumentatie beter te onderscheiden moeten zijn.
Brilliant Math & Science Wiki. (n.d.). Discrete Fourier Transform.
https://brilliant.org/wiki/discrete-fourier-transform/
Benameur, A. (2023, 30 augustus). Non-negative Matrix Factorization (NMF) vs Principal Component Analysis (PCA). OpenGenus IQ: Computing Expertise & Legacy.
https://iq.opengenus.org/nmf-vs-pca/
Hoorn.be - Muziektermen. (z.d.).
http://www.hoorn.be/muziektermen.htm
Jaadi, Z. (2023, 29 maart). A Step-by-Step Explanation of Principal Component Analysis (PCA). Built In.
https://builtin.com/data-science/step-step-explanation-principal-component-analysis
Kumar, N. (2021, 14 december). Unsupervised learning — Principal Component Analysis (PCA). Medium.
https://medium.com/analytics-vidhya/unsupervised-learning-principal-component-analysis-pca-dc94fecef09b
Krish Naik. (2018, 2 juli). Principle Component Analysis (PCA) using Sklearn and Python [Video]. YouTube.
https://www.youtube.com/watch?v=QdBy02ExhGI
Librosa.beat.beat_track — Librosa 0.10.1 Documentation. (z.d.).
https://librosa.org/doc/main/generated/librosa.beat.beat_track.html
Librosa.effects.hPss — Librosa 0.10.1 Documentation. (z.d.).
https://librosa.org/doc/main/generated/librosa.effects.hpss.html
librosa.feature.spectral_rolloff — librosa 0.10.1 documentation. (n.d.).
https://librosa.org/doc/main/generated/librosa.feature.spectral_rolloff.html
librosa.feature.tonnetz — librosa 0.10.1 documentation. (n.d.).
https://librosa.org/doc/main/generated/librosa.feature.tonnetz.html
Medium. (2021, December 10). MFCC’s Made Easy - Tanveer Singh.
https://medium.com/@tanveer9812/mfccs-made-easy-7ef383006040
Nam, U. (2001, April 28). Special Area Exam Part II.
https://ccrma.stanford.edu/~unjung/AIR/areaExam.pdf
Simplilearn. (2023, 7 november). What is dimensionality reduction? overview, and popular techniques.
https://www.simplilearn.com/what-is-dimensionality-reduction-article
Singh, T. (2021, December 10). MFCC’s Made Easy - Tanveer Singh - medium. Medium.
https://medium.com/@tanveer9812/mfccs-made-easy-7ef383006040
spectral_features. (n.d.).
https://musicinformationretrieval.com/spectral_features.html
StatQuest with Josh Starmer. (2018, 8 januari). StatQuest: PCA in Python [Video]. YouTube.
https://www.youtube.com/watch?v=Lsue2gEM9D0
Tempo - muziektheorie betekenis en uitleg tempo. (z.d.).
https://tweedehands-gitaar.nl/muziektheorie/begrippen/tempo
Wikipedia contributors. (2023, November 13). Mel-frequency cepstrum.
https://en.wikipedia.org/wiki/Mel-frequency_cepstrum